Skip to main content

stylex_path_resolver/package_json/
mod.rs

1use 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}