stylex_path_resolver/package_json/
mod.rs1use indexmap::IndexSet;
2use rustc_hash::FxHashMap;
3use serde::{Deserialize, Serialize};
4use std::{collections::HashMap, env};
5use std::{default::Default, fs::read_to_string};
6use stylex_macros::stylex_panic;
7
8use package_json::{PackageDependencies, PackageJsonManager};
9use std::path::{Path, PathBuf};
10
11use crate::file_system::find_closest_path;
12
13#[derive(Serialize, Deserialize, Debug, Default, Clone)]
14#[serde(rename_all = "camelCase")]
15pub struct PackageJsonExtended {
16 #[serde(skip_serializing_if = "Option::is_none")]
17 pub name: Option<String>,
18 #[serde(skip_serializing_if = "Option::is_none")]
19 pub main: Option<String>,
20 #[serde(skip_serializing_if = "Option::is_none")]
21 pub module: Option<String>,
22 #[serde(skip_serializing_if = "Option::is_none")]
23 pub dependencies: Option<PackageDependencies>,
24 #[serde(skip_serializing_if = "Option::is_none")]
25 pub dev_dependencies: Option<PackageDependencies>,
26}
27
28pub fn get_package_json(
29 path: &Path,
30 package_json_seen: &mut FxHashMap<String, PackageJsonExtended>,
31) -> (PackageJsonExtended, PackageJsonManager) {
32 let (package_json_path, manager) = get_package_json_path(path);
33
34 match package_json_path {
35 Some(file) => {
36 let file_path_string = file.display().to_string();
37 let file_path = file_path_string.as_str();
38 let data = package_json_seen.get(file_path).cloned().or_else(|| {
39 let data = read_to_string(file_path);
40
41 data.ok().map(|package_json_raw| {
42 let json = serde_json::from_str::<PackageJsonExtended>(package_json_raw.as_str())
43 .unwrap_or_else(|error| {
44 stylex_panic!(
45 "Failed to parse `{}` file. Error: {}.\n\nPossible reasons:\n\
46 - The JSON structure might be incorrect.\n\
47 - There might be a syntax error in the JSON.\n\
48 - Ensure all required fields are present and correctly formatted.",
49 file_path_string,
50 error
51 );
52 });
53
54 package_json_seen.insert(file_path.to_string(), json.clone());
55 json
56 })
57 });
58
59 match data {
60 Some(json) => (json, manager),
61 None => stylex_panic!(
62 "Failed to read package.json file: {}/{}",
63 env::current_dir().unwrap().display(),
64 file.display()
65 ),
66 }
67 },
68 None => {
69 stylex_panic!("No package.json found for path: {:?}", path.display());
70 },
71 }
72}
73
74pub(crate) fn get_package_json_path(path: &Path) -> (Option<PathBuf>, PackageJsonManager) {
75 let mut manager = PackageJsonManager::new();
76
77 match manager.locate_closest_from(path) {
78 Ok(file) => (Option::Some(file), manager),
79 Err(error) => {
80 stylex_panic!("Error: {}, path: {}", error, path.display());
81 },
82 }
83}
84
85pub fn find_closest_package_json(path: &Path) -> Option<PathBuf> {
86 find_closest_path(path, "package.json")
87}
88
89pub fn find_closest_package_json_folder(path: &Path) -> Option<PathBuf> {
90 find_closest_package_json(path).map(|path| path.parent().unwrap().to_path_buf())
91}
92
93pub fn find_closest_node_modules(path: &Path) -> Option<PathBuf> {
94 find_closest_path(path, "node_modules")
95}
96
97pub fn recursive_find_node_modules(
98 path: &Path,
99 known_git_dir: Option<PathBuf>,
100) -> IndexSet<PathBuf> {
101 let mut node_modules_paths = IndexSet::new();
102
103 if path.eq(Path::new("/")) {
104 let root_node_modules = path.join("node_modules");
105
106 if root_node_modules.exists() {
107 node_modules_paths.insert(root_node_modules);
108 }
109
110 return node_modules_paths;
111 }
112
113 let Some(closest_node_modules_path) = find_closest_node_modules(path) else {
114 return node_modules_paths;
115 };
116
117 node_modules_paths.insert(closest_node_modules_path.clone());
118
119 if let Some(repository_git_dir) = known_git_dir.or_else(|| find_closest_path(path, ".git")) {
120 let repository_root = repository_git_dir.parent().unwrap_or(Path::new("/"));
121
122 let closest_modules_parent = closest_node_modules_path
123 .parent()
124 .and_then(|p| p.parent())
125 .unwrap_or(Path::new("/"));
126
127 if repository_root.eq(closest_modules_parent) {
128 let root_node_modules = repository_root.join("node_modules");
129
130 if root_node_modules.exists() {
131 node_modules_paths.insert(root_node_modules);
132 }
133 } else if closest_modules_parent.starts_with(repository_root) {
134 node_modules_paths.extend(recursive_find_node_modules(
135 closest_modules_parent,
136 Some(repository_git_dir),
137 ));
138 }
139 }
140
141 node_modules_paths
142}
143
144pub(crate) fn get_package_json_deps(
145 cwd: &Path,
146 package_json_seen: &mut FxHashMap<String, PackageJsonExtended>,
147) -> HashMap<String, String> {
148 let (package_json, _) = get_package_json(cwd, package_json_seen);
149
150 let mut package_dependencies = package_json.dependencies.unwrap_or_default();
151 let package_dev_dependencies = package_json.dev_dependencies.unwrap_or_default();
152
153 package_dependencies.extend(package_dev_dependencies);
154
155 package_dependencies
156}