1use chrono::{DateTime, Local};
  2use collections::{BTreeSet, HashMap};
  3use serde_json_lenient::Value;
  4use std::{path::Path, sync::Arc};
  5
  6#[derive(Clone, Debug)]
  7pub struct PackageJson {
  8    pub mtime: DateTime<Local>,
  9    pub data: PackageJsonData,
 10}
 11
 12#[derive(Clone, Debug, Default, PartialEq, Eq)]
 13pub struct PackageJsonData {
 14    pub jest_package_path: Option<Arc<Path>>,
 15    pub mocha_package_path: Option<Arc<Path>>,
 16    pub vitest_package_path: Option<Arc<Path>>,
 17    pub jasmine_package_path: Option<Arc<Path>>,
 18    pub bun_package_path: Option<Arc<Path>>,
 19    pub node_package_path: Option<Arc<Path>>,
 20    pub scripts: BTreeSet<(Arc<Path>, String)>,
 21    pub package_manager: Option<&'static str>,
 22}
 23
 24impl PackageJsonData {
 25    pub fn new(path: Arc<Path>, package_json: HashMap<String, Value>) -> Self {
 26        let mut scripts = BTreeSet::new();
 27        if let Some(Value::Object(package_json_scripts)) = package_json.get("scripts") {
 28            scripts.extend(
 29                package_json_scripts
 30                    .keys()
 31                    .cloned()
 32                    .map(|name| (path.clone(), name)),
 33            );
 34        }
 35
 36        let mut jest_package_path = None;
 37        let mut mocha_package_path = None;
 38        let mut vitest_package_path = None;
 39        let mut jasmine_package_path = None;
 40        let mut bun_package_path = None;
 41        let mut node_package_path = None;
 42        if let Some(Value::Object(dependencies)) = package_json.get("devDependencies") {
 43            if dependencies.contains_key("jest") {
 44                jest_package_path.get_or_insert_with(|| path.clone());
 45            }
 46            if dependencies.contains_key("mocha") {
 47                mocha_package_path.get_or_insert_with(|| path.clone());
 48            }
 49            if dependencies.contains_key("vitest") {
 50                vitest_package_path.get_or_insert_with(|| path.clone());
 51            }
 52            if dependencies.contains_key("jasmine") {
 53                jasmine_package_path.get_or_insert_with(|| path.clone());
 54            }
 55            if dependencies.contains_key("@types/bun") {
 56                bun_package_path.get_or_insert_with(|| path.clone());
 57            }
 58            if dependencies.contains_key("@types/node") {
 59                node_package_path.get_or_insert_with(|| path.clone());
 60            }
 61        }
 62        if let Some(Value::Object(dev_dependencies)) = package_json.get("dependencies") {
 63            if dev_dependencies.contains_key("jest") {
 64                jest_package_path.get_or_insert_with(|| path.clone());
 65            }
 66            if dev_dependencies.contains_key("mocha") {
 67                mocha_package_path.get_or_insert_with(|| path.clone());
 68            }
 69            if dev_dependencies.contains_key("vitest") {
 70                vitest_package_path.get_or_insert_with(|| path.clone());
 71            }
 72            if dev_dependencies.contains_key("jasmine") {
 73                jasmine_package_path.get_or_insert_with(|| path.clone());
 74            }
 75            if dev_dependencies.contains_key("@types/bun") {
 76                bun_package_path.get_or_insert_with(|| path.clone());
 77            }
 78            if dev_dependencies.contains_key("@types/node") {
 79                node_package_path.get_or_insert_with(|| path.clone());
 80            }
 81        }
 82
 83        let package_manager = package_json
 84            .get("packageManager")
 85            .and_then(|value| value.as_str())
 86            .and_then(|value| {
 87                if value.starts_with("pnpm") {
 88                    Some("pnpm")
 89                } else if value.starts_with("yarn") {
 90                    Some("yarn")
 91                } else if value.starts_with("npm") {
 92                    Some("npm")
 93                } else if value.starts_with("bun") {
 94                    Some("bun")
 95                } else {
 96                    None
 97                }
 98            });
 99
100        Self {
101            jest_package_path,
102            mocha_package_path,
103            vitest_package_path,
104            jasmine_package_path,
105            bun_package_path,
106            node_package_path,
107            scripts,
108            package_manager,
109        }
110    }
111
112    pub fn merge(&mut self, other: Self) {
113        self.jest_package_path = self.jest_package_path.take().or(other.jest_package_path);
114        self.mocha_package_path = self.mocha_package_path.take().or(other.mocha_package_path);
115        self.vitest_package_path = self
116            .vitest_package_path
117            .take()
118            .or(other.vitest_package_path);
119        self.jasmine_package_path = self
120            .jasmine_package_path
121            .take()
122            .or(other.jasmine_package_path);
123        self.bun_package_path = self.bun_package_path.take().or(other.bun_package_path);
124        self.node_package_path = self.node_package_path.take().or(other.node_package_path);
125        self.scripts.extend(other.scripts);
126        self.package_manager = self.package_manager.or(other.package_manager);
127    }
128}