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}