node_resolver/
package_json.rs

1// Copyright 2018-2025 the Deno authors. MIT license.
2
3use std::cell::RefCell;
4use std::collections::HashMap;
5use std::path::Path;
6use std::path::PathBuf;
7
8use deno_package_json::PackageJson;
9use deno_package_json::PackageJsonCacheResult;
10use deno_package_json::PackageJsonRc;
11use sys_traits::FsMetadata;
12use sys_traits::FsRead;
13
14use crate::errors::PackageJsonLoadError;
15
16pub trait NodePackageJsonCache:
17  deno_package_json::PackageJsonCache
18  + std::fmt::Debug
19  + deno_maybe_sync::MaybeSend
20  + deno_maybe_sync::MaybeSync
21{
22  fn as_deno_package_json_cache(
23    &self,
24  ) -> &dyn deno_package_json::PackageJsonCache;
25}
26
27impl<T> NodePackageJsonCache for T
28where
29  T: deno_package_json::PackageJsonCache
30    + std::fmt::Debug
31    + deno_maybe_sync::MaybeSend
32    + deno_maybe_sync::MaybeSync,
33{
34  fn as_deno_package_json_cache(
35    &self,
36  ) -> &dyn deno_package_json::PackageJsonCache {
37    self
38  }
39}
40
41#[allow(clippy::disallowed_types)]
42pub type PackageJsonCacheRc =
43  deno_maybe_sync::MaybeArc<dyn NodePackageJsonCache>;
44
45thread_local! {
46  static CACHE: RefCell<HashMap<PathBuf, PackageJsonRc>> = RefCell::new(HashMap::new());
47}
48
49#[derive(Debug)]
50pub struct PackageJsonThreadLocalCache;
51
52impl PackageJsonThreadLocalCache {
53  pub fn clear() {
54    CACHE.with_borrow_mut(|cache| cache.clear());
55  }
56}
57
58impl deno_package_json::PackageJsonCache for PackageJsonThreadLocalCache {
59  fn get(&self, path: &Path) -> PackageJsonCacheResult {
60    CACHE.with_borrow(|cache| match cache.get(path).cloned() {
61      Some(value) => PackageJsonCacheResult::Hit(Some(value)),
62      None => PackageJsonCacheResult::NotCached,
63    })
64  }
65
66  fn set(&self, path: PathBuf, package_json: Option<PackageJsonRc>) {
67    let Some(package_json) = package_json else {
68      // We don't cache misses.
69      return;
70    };
71    CACHE.with_borrow_mut(|cache| cache.insert(path, package_json));
72  }
73}
74
75#[allow(clippy::disallowed_types)]
76pub type PackageJsonResolverRc<TSys> =
77  deno_maybe_sync::MaybeArc<PackageJsonResolver<TSys>>;
78
79#[derive(Debug)]
80pub struct PackageJsonResolver<TSys: FsRead + FsMetadata> {
81  sys: TSys,
82  loader_cache: Option<PackageJsonCacheRc>,
83}
84
85impl<TSys: FsRead + FsMetadata> PackageJsonResolver<TSys> {
86  pub fn new(sys: TSys, loader_cache: Option<PackageJsonCacheRc>) -> Self {
87    Self { sys, loader_cache }
88  }
89
90  pub fn get_closest_package_json(
91    &self,
92    file_path: &Path,
93  ) -> Result<Option<PackageJsonRc>, PackageJsonLoadError> {
94    self.get_closest_package_jsons(file_path).next().transpose()
95  }
96
97  /// Gets the closest package.json files, iterating from the
98  /// nearest directory to the furthest ancestor directory.
99  pub fn get_closest_package_jsons<'a>(
100    &'a self,
101    file_path: &'a Path,
102  ) -> ClosestPackageJsonsIterator<'a, TSys> {
103    ClosestPackageJsonsIterator {
104      current_path: file_path,
105      resolver: self,
106    }
107  }
108
109  pub fn load_package_json(
110    &self,
111    path: &Path,
112  ) -> Result<Option<PackageJsonRc>, PackageJsonLoadError> {
113    let result = PackageJson::load_from_path(
114      &self.sys,
115      self
116        .loader_cache
117        .as_deref()
118        .map(|cache| cache.as_deno_package_json_cache()),
119      path,
120    );
121    match result {
122      Ok(pkg_json) => Ok(pkg_json),
123      Err(err) => Err(PackageJsonLoadError(err)),
124    }
125  }
126}
127
128pub struct ClosestPackageJsonsIterator<'a, TSys: FsRead + FsMetadata> {
129  current_path: &'a Path,
130  resolver: &'a PackageJsonResolver<TSys>,
131}
132
133impl<'a, TSys: FsRead + FsMetadata> Iterator
134  for ClosestPackageJsonsIterator<'a, TSys>
135{
136  type Item = Result<PackageJsonRc, PackageJsonLoadError>;
137
138  fn next(&mut self) -> Option<Self::Item> {
139    while let Some(parent) = self.current_path.parent() {
140      self.current_path = parent;
141      let package_json_path = parent.join("package.json");
142      match self.resolver.load_package_json(&package_json_path) {
143        Ok(Some(value)) => return Some(Ok(value)),
144        Ok(None) => {
145          // skip
146        }
147        Err(err) => return Some(Err(err)),
148      }
149    }
150    None
151  }
152}