node_resolver/
resolution.rs

1// Copyright 2018-2025 the Deno authors. MIT license.
2
3use std::borrow::Cow;
4use std::collections::BTreeMap;
5use std::fmt::Debug;
6use std::path::Path;
7use std::path::PathBuf;
8
9use anyhow::Error as AnyError;
10use anyhow::bail;
11use deno_media_type::MediaType;
12use deno_package_json::PackageJson;
13use deno_package_json::PackageJsonRc;
14use deno_path_util::url_to_file_path;
15use deno_semver::Version;
16use deno_semver::VersionReq;
17use lazy_regex::Lazy;
18use regex::Regex;
19use serde_json::Map;
20use serde_json::Value;
21use sys_traits::FileType;
22use sys_traits::FsCanonicalize;
23use sys_traits::FsDirEntry;
24use sys_traits::FsMetadata;
25use sys_traits::FsRead;
26use sys_traits::FsReadDir;
27use url::Url;
28
29use crate::InNpmPackageChecker;
30use crate::IsBuiltInNodeModuleChecker;
31use crate::NpmPackageFolderResolver;
32use crate::PackageJsonResolverRc;
33use crate::PathClean;
34use crate::cache::NodeResolutionSys;
35use crate::errors;
36use crate::errors::DataUrlReferrerError;
37use crate::errors::FinalizeResolutionError;
38use crate::errors::InvalidModuleSpecifierError;
39use crate::errors::InvalidPackageTargetError;
40use crate::errors::LegacyResolveError;
41use crate::errors::MissingPkgJsonError;
42use crate::errors::ModuleNotFoundError;
43use crate::errors::NodeJsErrorCode;
44use crate::errors::NodeJsErrorCoded;
45use crate::errors::NodeResolveError;
46use crate::errors::NodeResolveRelativeJoinError;
47use crate::errors::PackageExportsResolveError;
48use crate::errors::PackageImportNotDefinedError;
49use crate::errors::PackageImportsResolveError;
50use crate::errors::PackageImportsResolveErrorKind;
51use crate::errors::PackagePathNotExportedError;
52use crate::errors::PackageResolveError;
53use crate::errors::PackageSubpathFromDenoModuleResolveError;
54use crate::errors::PackageSubpathResolveError;
55use crate::errors::PackageSubpathResolveErrorKind;
56use crate::errors::PackageTargetNotFoundError;
57use crate::errors::PackageTargetResolveError;
58use crate::errors::PackageTargetResolveErrorKind;
59use crate::errors::ResolvePkgJsonBinExportError;
60use crate::errors::ResolvePkgNpmBinaryCommandsError;
61use crate::errors::TypesNotFoundError;
62use crate::errors::TypesNotFoundErrorData;
63use crate::errors::UnknownBuiltInNodeModuleError;
64use crate::errors::UnsupportedDirImportError;
65use crate::errors::UnsupportedEsmUrlSchemeError;
66use crate::path::UrlOrPath;
67use crate::path::UrlOrPathRef;
68
69pub static IMPORT_CONDITIONS: &[Cow<'static, str>] = &[
70  Cow::Borrowed("deno"),
71  Cow::Borrowed("node"),
72  Cow::Borrowed("import"),
73];
74pub static REQUIRE_CONDITIONS: &[Cow<'static, str>] =
75  &[Cow::Borrowed("require"), Cow::Borrowed("node")];
76static TYPES_ONLY_CONDITIONS: &[Cow<'static, str>] = &[Cow::Borrowed("types")];
77
78#[derive(Debug, Default, Clone)]
79pub struct NodeConditionOptions {
80  pub conditions: Vec<Cow<'static, str>>,
81  /// Provide a value to override the default import conditions.
82  ///
83  /// Defaults to `["deno", "node", "import"]`
84  pub import_conditions_override: Option<Vec<Cow<'static, str>>>,
85  /// Provide a value to override the default require conditions.
86  ///
87  /// Defaults to `["require", "node"]`
88  pub require_conditions_override: Option<Vec<Cow<'static, str>>>,
89}
90
91#[derive(Debug, Clone)]
92struct ConditionResolver {
93  import_conditions: Cow<'static, [Cow<'static, str>]>,
94  require_conditions: Cow<'static, [Cow<'static, str>]>,
95}
96
97impl ConditionResolver {
98  pub fn new(options: NodeConditionOptions) -> Self {
99    fn combine_conditions(
100      user_conditions: Cow<'_, [Cow<'static, str>]>,
101      override_default: Option<Vec<Cow<'static, str>>>,
102      default_conditions: &'static [Cow<'static, str>],
103    ) -> Cow<'static, [Cow<'static, str>]> {
104      let default_conditions = override_default
105        .map(Cow::Owned)
106        .unwrap_or(Cow::Borrowed(default_conditions));
107      if user_conditions.is_empty() {
108        default_conditions
109      } else {
110        let mut new =
111          Vec::with_capacity(user_conditions.len() + default_conditions.len());
112        let mut append =
113          |conditions: Cow<'_, [Cow<'static, str>]>| match conditions {
114            Cow::Borrowed(conditions) => new.extend(conditions.iter().cloned()),
115            Cow::Owned(conditions) => new.extend(conditions),
116          };
117        append(user_conditions);
118        append(default_conditions);
119        Cow::Owned(new)
120      }
121    }
122
123    Self {
124      import_conditions: combine_conditions(
125        Cow::Borrowed(&options.conditions),
126        options.import_conditions_override,
127        IMPORT_CONDITIONS,
128      ),
129      require_conditions: combine_conditions(
130        Cow::Owned(options.conditions),
131        options.require_conditions_override,
132        REQUIRE_CONDITIONS,
133      ),
134    }
135  }
136
137  pub fn resolve(
138    &self,
139    resolution_mode: ResolutionMode,
140  ) -> &[Cow<'static, str>] {
141    match resolution_mode {
142      ResolutionMode::Import => &self.import_conditions,
143      ResolutionMode::Require => &self.require_conditions,
144    }
145  }
146
147  pub fn require_conditions(&self) -> &[Cow<'static, str>] {
148    &self.require_conditions
149  }
150}
151
152#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
153pub enum ResolutionMode {
154  Import,
155  Require,
156}
157
158impl ResolutionMode {
159  pub fn default_conditions(&self) -> &'static [Cow<'static, str>] {
160    match self {
161      ResolutionMode::Import => IMPORT_CONDITIONS,
162      ResolutionMode::Require => REQUIRE_CONDITIONS,
163    }
164  }
165
166  #[cfg(feature = "graph")]
167  pub fn from_deno_graph(mode: deno_graph::source::ResolutionMode) -> Self {
168    use deno_graph::source::ResolutionMode::*;
169    match mode {
170      Import => Self::Import,
171      Require => Self::Require,
172    }
173  }
174}
175
176#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
177pub enum NodeResolutionKind {
178  Execution,
179  Types,
180}
181
182impl NodeResolutionKind {
183  pub fn is_types(&self) -> bool {
184    matches!(self, NodeResolutionKind::Types)
185  }
186
187  #[cfg(feature = "graph")]
188  pub fn from_deno_graph(kind: deno_graph::source::ResolutionKind) -> Self {
189    use deno_graph::source::ResolutionKind::*;
190    match kind {
191      Execution => Self::Execution,
192      Types => Self::Types,
193    }
194  }
195}
196
197#[derive(Debug)]
198pub enum NodeResolution {
199  Module(UrlOrPath),
200  BuiltIn(String),
201}
202
203impl NodeResolution {
204  pub fn into_url(self) -> Result<Url, NodeResolveError> {
205    match self {
206      Self::Module(u) => Ok(u.into_url()?),
207      Self::BuiltIn(specifier) => Ok(if specifier.starts_with("node:") {
208        Url::parse(&specifier).unwrap()
209      } else {
210        Url::parse(&format!("node:{specifier}")).unwrap()
211      }),
212    }
213  }
214}
215
216struct LocalPath {
217  path: PathBuf,
218  known_exists: bool,
219}
220
221enum LocalUrlOrPath {
222  Url(Url),
223  Path(LocalPath),
224}
225
226impl LocalUrlOrPath {
227  pub fn into_url_or_path(self) -> UrlOrPath {
228    match self {
229      LocalUrlOrPath::Url(url) => UrlOrPath::Url(url),
230      LocalUrlOrPath::Path(local_path) => UrlOrPath::Path(local_path.path),
231    }
232  }
233}
234
235/// This struct helps ensure we remember to probe for
236/// declaration files and to prevent accidentally probing
237/// multiple times.
238struct MaybeTypesResolvedUrl(LocalUrlOrPath);
239
240/// Kind of method that resolution suceeded with.
241enum ResolvedMethod {
242  Url,
243  RelativeOrAbsolute,
244  PackageImports,
245  PackageExports,
246  PackageSubPath,
247}
248
249#[derive(Debug, Default, Clone)]
250pub struct NodeResolverOptions {
251  pub conditions: NodeConditionOptions,
252  pub is_browser_platform: bool,
253  pub bundle_mode: bool,
254  /// TypeScript version to use for typesVersions resolution and
255  /// `types@req` exports resolution.
256  pub typescript_version: Option<Version>,
257}
258
259#[derive(Debug)]
260struct ResolutionConfig {
261  pub bundle_mode: bool,
262  pub prefer_browser_field: bool,
263  pub typescript_version: Option<Version>,
264}
265
266#[sys_traits::auto_impl]
267pub trait NodeResolverSys:
268  FsCanonicalize + FsMetadata + FsRead + FsReadDir
269{
270}
271
272#[allow(clippy::disallowed_types)]
273pub type NodeResolverRc<
274  TInNpmPackageChecker,
275  TIsBuiltInNodeModuleChecker,
276  TNpmPackageFolderResolver,
277  TSys,
278> = deno_maybe_sync::MaybeArc<
279  NodeResolver<
280    TInNpmPackageChecker,
281    TIsBuiltInNodeModuleChecker,
282    TNpmPackageFolderResolver,
283    TSys,
284  >,
285>;
286
287#[derive(Debug)]
288pub struct NodeResolver<
289  TInNpmPackageChecker: InNpmPackageChecker,
290  TIsBuiltInNodeModuleChecker: IsBuiltInNodeModuleChecker,
291  TNpmPackageFolderResolver: NpmPackageFolderResolver,
292  TSys: NodeResolverSys,
293> {
294  in_npm_pkg_checker: TInNpmPackageChecker,
295  is_built_in_node_module_checker: TIsBuiltInNodeModuleChecker,
296  npm_pkg_folder_resolver: TNpmPackageFolderResolver,
297  pkg_json_resolver: PackageJsonResolverRc<TSys>,
298  sys: NodeResolutionSys<TSys>,
299  condition_resolver: ConditionResolver,
300  resolution_config: ResolutionConfig,
301}
302
303impl<
304  TInNpmPackageChecker: InNpmPackageChecker,
305  TIsBuiltInNodeModuleChecker: IsBuiltInNodeModuleChecker,
306  TNpmPackageFolderResolver: NpmPackageFolderResolver,
307  TSys: NodeResolverSys,
308>
309  NodeResolver<
310    TInNpmPackageChecker,
311    TIsBuiltInNodeModuleChecker,
312    TNpmPackageFolderResolver,
313    TSys,
314  >
315{
316  pub fn new(
317    in_npm_pkg_checker: TInNpmPackageChecker,
318    is_built_in_node_module_checker: TIsBuiltInNodeModuleChecker,
319    npm_pkg_folder_resolver: TNpmPackageFolderResolver,
320    pkg_json_resolver: PackageJsonResolverRc<TSys>,
321    sys: NodeResolutionSys<TSys>,
322    options: NodeResolverOptions,
323  ) -> Self {
324    Self {
325      in_npm_pkg_checker,
326      is_built_in_node_module_checker,
327      npm_pkg_folder_resolver,
328      pkg_json_resolver,
329      sys,
330      condition_resolver: ConditionResolver::new(NodeConditionOptions {
331        conditions: options.conditions.conditions,
332        import_conditions_override: options
333          .conditions
334          .import_conditions_override
335          .or_else(|| {
336            if options.is_browser_platform {
337              Some(vec![Cow::Borrowed("browser"), Cow::Borrowed("import")])
338            } else {
339              None
340            }
341          }),
342        require_conditions_override: options
343          .conditions
344          .require_conditions_override
345          .or_else(|| {
346            if options.is_browser_platform {
347              Some(vec![Cow::Borrowed("browser"), Cow::Borrowed("require")])
348            } else {
349              None
350            }
351          }),
352      }),
353      resolution_config: ResolutionConfig {
354        bundle_mode: options.bundle_mode,
355        prefer_browser_field: options.is_browser_platform,
356        typescript_version: options.typescript_version,
357      },
358    }
359  }
360
361  pub fn require_conditions(&self) -> &[Cow<'static, str>] {
362    self.condition_resolver.require_conditions()
363  }
364
365  pub fn in_npm_package(&self, specifier: &Url) -> bool {
366    self.in_npm_pkg_checker.in_npm_package(specifier)
367  }
368
369  #[inline(always)]
370  pub fn is_builtin_node_module(&self, specifier: &str) -> bool {
371    self
372      .is_built_in_node_module_checker
373      .is_builtin_node_module(specifier)
374  }
375
376  /// This function is an implementation of `defaultResolve` in
377  /// `lib/internal/modules/esm/resolve.js` from Node.
378  pub fn resolve(
379    &self,
380    specifier: &str,
381    referrer: &Url,
382    resolution_mode: ResolutionMode,
383    resolution_kind: NodeResolutionKind,
384  ) -> Result<NodeResolution, NodeResolveError> {
385    // Note: if we are here, then the referrer is an esm module
386    // TODO(bartlomieju): skipped "policy" part as we don't plan to support it
387
388    if self.is_builtin_node_module(specifier) {
389      return Ok(NodeResolution::BuiltIn(specifier.to_string()));
390    }
391
392    if let Ok(url) = Url::parse(specifier) {
393      if url.scheme() == "data" {
394        return Ok(NodeResolution::Module(UrlOrPath::Url(url)));
395      }
396
397      if let Some(module_name) =
398        self.get_module_name_from_builtin_node_module_url(&url)?
399      {
400        return Ok(NodeResolution::BuiltIn(module_name.to_string()));
401      }
402
403      let protocol = url.scheme();
404
405      if protocol != "file" && protocol != "data" {
406        return Err(
407          UnsupportedEsmUrlSchemeError {
408            url_scheme: protocol.to_string(),
409          }
410          .into(),
411        );
412      }
413
414      // todo(dsherret): this seems wrong
415      if referrer.scheme() == "data" {
416        let url = referrer
417          .join(specifier)
418          .map_err(|source| DataUrlReferrerError { source })?;
419        return Ok(NodeResolution::Module(UrlOrPath::Url(url)));
420      }
421    }
422
423    let conditions = self.condition_resolver.resolve(resolution_mode);
424    let referrer = UrlOrPathRef::from_url(referrer);
425    let (url, resolved_kind) = self.module_resolve(
426      specifier,
427      &referrer,
428      resolution_mode,
429      conditions,
430      resolution_kind,
431    )?;
432
433    let url_or_path = self.finalize_resolution(
434      url,
435      resolved_kind,
436      resolution_mode,
437      conditions,
438      resolution_kind,
439      Some(&referrer),
440    )?;
441    let resolve_response = NodeResolution::Module(url_or_path);
442    // TODO(bartlomieju): skipped checking errors for commonJS resolution and
443    // "preserveSymlinksMain"/"preserveSymlinks" options.
444    Ok(resolve_response)
445  }
446
447  fn module_resolve(
448    &self,
449    specifier: &str,
450    referrer: &UrlOrPathRef,
451    resolution_mode: ResolutionMode,
452    conditions: &[Cow<'static, str>],
453    resolution_kind: NodeResolutionKind,
454  ) -> Result<(MaybeTypesResolvedUrl, ResolvedMethod), NodeResolveError> {
455    if should_be_treated_as_relative_or_absolute_path(specifier) {
456      let referrer_url = referrer.url()?;
457      let url = node_join_url(referrer_url, specifier).map_err(|err| {
458        NodeResolveRelativeJoinError {
459          path: specifier.to_string(),
460          base: referrer_url.clone(),
461          source: err,
462        }
463      })?;
464      let url = self.maybe_resolve_types(
465        LocalUrlOrPath::Url(url),
466        Some(referrer),
467        resolution_mode,
468        conditions,
469        resolution_kind,
470      )?;
471      Ok((url, ResolvedMethod::RelativeOrAbsolute))
472    } else if specifier.starts_with('#') {
473      let pkg_config = self
474        .pkg_json_resolver
475        .get_closest_package_json(referrer.path()?)
476        .map_err(PackageImportsResolveErrorKind::PkgJsonLoad)
477        .map_err(|err| PackageImportsResolveError(Box::new(err)))?;
478      Ok((
479        self.package_imports_resolve_internal(
480          specifier,
481          Some(referrer),
482          resolution_mode,
483          pkg_config.as_deref(),
484          conditions,
485          resolution_kind,
486        )?,
487        ResolvedMethod::PackageImports,
488      ))
489    } else if let Ok(url) = Url::parse(specifier) {
490      let url_or_path = self.maybe_resolve_types(
491        LocalUrlOrPath::Url(url),
492        Some(referrer),
493        resolution_mode,
494        conditions,
495        resolution_kind,
496      )?;
497      Ok((url_or_path, ResolvedMethod::Url))
498    } else {
499      Ok(self.package_resolve(
500        specifier,
501        referrer,
502        resolution_mode,
503        conditions,
504        resolution_kind,
505      )?)
506    }
507  }
508
509  fn finalize_resolution(
510    &self,
511    resolved: MaybeTypesResolvedUrl,
512    resolved_method: ResolvedMethod,
513    resolution_mode: ResolutionMode,
514    conditions: &[Cow<'static, str>],
515    resolution_kind: NodeResolutionKind,
516    maybe_referrer: Option<&UrlOrPathRef>,
517  ) -> Result<UrlOrPath, FinalizeResolutionError> {
518    let encoded_sep_re = lazy_regex::regex!(r"%2F|%2C");
519
520    let resolved = resolved.0;
521    let text = match &resolved {
522      LocalUrlOrPath::Url(url) => Cow::Borrowed(url.as_str()),
523      LocalUrlOrPath::Path(LocalPath { path, .. }) => path.to_string_lossy(),
524    };
525    if encoded_sep_re.is_match(&text) {
526      return Err(
527        errors::InvalidModuleSpecifierError {
528          request: text.into_owned(),
529          reason: Cow::Borrowed(
530            "must not include encoded \"/\" or \"\\\\\" characters",
531          ),
532          maybe_referrer: maybe_referrer.map(|r| match r.path() {
533            // in this case, prefer showing the path string
534            Ok(path) => path.display().to_string(),
535            Err(_) => r.display().to_string(),
536          }),
537        }
538        .into(),
539      );
540    }
541
542    let (path, maybe_url) = match resolved {
543      LocalUrlOrPath::Url(url) => {
544        if url.scheme() == "file" {
545          (url_to_file_path(&url)?, Some(url))
546        } else {
547          return Ok(UrlOrPath::Url(url));
548        }
549      }
550      LocalUrlOrPath::Path(LocalPath { path, known_exists }) => {
551        if known_exists {
552          // no need to do the finalization checks
553          return Ok(UrlOrPath::Path(path));
554        } else {
555          (path, None)
556        }
557      }
558    };
559
560    // TODO(bartlomieju): currently not supported
561    // if (getOptionValue('--experimental-specifier-resolution') === 'node') {
562    //   ...
563    // }
564
565    let p_str = path.to_str().unwrap();
566    let path = match p_str.strip_suffix('/') {
567      Some(s) => Cow::Borrowed(Path::new(s)),
568      None => Cow::Owned(path),
569    };
570
571    let maybe_file_type = self.sys.get_file_type(&path);
572    match maybe_file_type {
573      Ok(FileType::Dir) => {
574        if resolution_mode == ResolutionMode::Import
575          && !self.resolution_config.bundle_mode
576        {
577          let suggestion = self.directory_import_suggestion(&path);
578          Err(
579            UnsupportedDirImportError {
580              dir_url: UrlOrPath::Path(path.into_owned()),
581              maybe_referrer: maybe_referrer.map(|r| r.display()),
582              suggestion,
583            }
584            .into(),
585          )
586        } else {
587          // prefer the file over the directory
588          let path_with_ext = with_known_extension(&path, "js");
589          if self.sys.is_file(&path_with_ext) {
590            Ok(UrlOrPath::Path(path_with_ext))
591          } else {
592            let (resolved_url, resolved_method) = self
593              .resolve_package_dir_subpath(
594                &path,
595                ".",
596                maybe_referrer,
597                resolution_mode,
598                conditions,
599                resolution_kind,
600              )?;
601            self.finalize_resolution(
602              resolved_url,
603              resolved_method,
604              resolution_mode,
605              conditions,
606              resolution_kind,
607              maybe_referrer,
608            )
609          }
610        }
611      }
612      Ok(FileType::File) => {
613        // prefer returning the url to avoid re-allocating in the CLI crate
614        Ok(
615          maybe_url
616            .map(UrlOrPath::Url)
617            .unwrap_or(UrlOrPath::Path(path.into_owned())),
618        )
619      }
620      _ => {
621        if let Err(e) = maybe_file_type
622          && (resolution_mode == ResolutionMode::Require
623            || self.resolution_config.bundle_mode)
624          && e.kind() == std::io::ErrorKind::NotFound
625        {
626          let file_with_ext = with_known_extension(&path, "js");
627          if self.sys.is_file(&file_with_ext) {
628            return Ok(UrlOrPath::Path(file_with_ext));
629          }
630        }
631
632        Err(
633          ModuleNotFoundError {
634            suggested_ext: self
635              .module_not_found_ext_suggestion(&path, resolved_method),
636            specifier: UrlOrPath::Path(path.into_owned()),
637            maybe_referrer: maybe_referrer.map(|r| r.display()),
638          }
639          .into(),
640        )
641      }
642    }
643  }
644
645  fn module_not_found_ext_suggestion(
646    &self,
647    path: &Path,
648    resolved_method: ResolvedMethod,
649  ) -> Option<&'static str> {
650    fn should_probe(path: &Path, resolved_method: ResolvedMethod) -> bool {
651      if MediaType::from_path(path) != MediaType::Unknown {
652        return false;
653      }
654      match resolved_method {
655        ResolvedMethod::Url
656        | ResolvedMethod::RelativeOrAbsolute
657        | ResolvedMethod::PackageSubPath => true,
658        ResolvedMethod::PackageImports | ResolvedMethod::PackageExports => {
659          false
660        }
661      }
662    }
663
664    if should_probe(path, resolved_method) {
665      ["js", "mjs", "cjs"]
666        .into_iter()
667        .find(|ext| self.sys.is_file(&with_known_extension(path, ext)))
668    } else {
669      None
670    }
671  }
672
673  fn directory_import_suggestion(
674    &self,
675    dir_import_path: &Path,
676  ) -> Option<String> {
677    let dir_index_paths = ["index.mjs", "index.js", "index.cjs"]
678      .into_iter()
679      .map(|file_name| dir_import_path.join(file_name));
680    let file_paths = [
681      with_known_extension(dir_import_path, "js"),
682      with_known_extension(dir_import_path, "mjs"),
683      with_known_extension(dir_import_path, "cjs"),
684    ];
685    dir_index_paths
686      .chain(file_paths)
687      .chain(
688        std::iter::once_with(|| {
689          // check if this directory has a package.json
690          let package_json_path = dir_import_path.join("package.json");
691          let pkg_json = self
692            .pkg_json_resolver
693            .load_package_json(&package_json_path)
694            .ok()??;
695          let main = pkg_json.main.as_ref()?;
696          Some(dir_import_path.join(main))
697        })
698        .flatten(),
699      )
700      .map(|p| deno_path_util::normalize_path(Cow::Owned(p)))
701      .find(|p| self.sys.is_file(p))
702      .and_then(|suggested_file_path| {
703        let pkg_json = self
704          .pkg_json_resolver
705          .get_closest_package_jsons(&suggested_file_path)
706          .filter_map(|pkg_json| pkg_json.ok())
707          .find(|p| p.name.is_some())?;
708        let pkg_name = pkg_json.name.as_ref()?;
709        let sub_path = suggested_file_path
710          .strip_prefix(pkg_json.dir_path())
711          .ok()?
712          .to_string_lossy()
713          .replace("\\", "/");
714        Some(format!("{}/{}", pkg_name, sub_path))
715      })
716  }
717
718  pub fn resolve_package_subpath_from_deno_module(
719    &self,
720    package_dir: &Path,
721    package_subpath: Option<&str>,
722    maybe_referrer: Option<&Url>,
723    resolution_mode: ResolutionMode,
724    resolution_kind: NodeResolutionKind,
725  ) -> Result<UrlOrPath, PackageSubpathFromDenoModuleResolveError> {
726    // todo(dsherret): don't allocate a string here (maybe use an
727    // enum that says the subpath is not prefixed with a ./)
728    let package_subpath = package_subpath
729      .map(|s| Cow::Owned(format!("./{s}")))
730      .unwrap_or_else(|| Cow::Borrowed("."));
731    let maybe_referrer = maybe_referrer.map(UrlOrPathRef::from_url);
732    let conditions = self.condition_resolver.resolve(resolution_mode);
733    let (resolved_url, resolved_method) = self.resolve_package_dir_subpath(
734      package_dir,
735      &package_subpath,
736      maybe_referrer.as_ref(),
737      resolution_mode,
738      conditions,
739      resolution_kind,
740    )?;
741    let url_or_path = self.finalize_resolution(
742      resolved_url,
743      resolved_method,
744      resolution_mode,
745      conditions,
746      resolution_kind,
747      maybe_referrer.as_ref(),
748    )?;
749    Ok(url_or_path)
750  }
751
752  pub fn resolve_binary_export(
753    &self,
754    package_folder: &Path,
755    sub_path: Option<&str>,
756  ) -> Result<PathBuf, ResolvePkgJsonBinExportError> {
757    let (pkg_json, items) = self
758      .resolve_npm_binary_commands_for_package_with_pkg_json(package_folder)?;
759    let path =
760      resolve_bin_entry_value(&pkg_json, &items, sub_path).map_err(|err| {
761        ResolvePkgJsonBinExportError::InvalidBinProperty {
762          message: err.to_string(),
763        }
764      })?;
765    Ok(path.to_path_buf())
766  }
767
768  pub fn resolve_npm_binary_commands_for_package(
769    &self,
770    package_folder: &Path,
771  ) -> Result<BTreeMap<String, PathBuf>, ResolvePkgNpmBinaryCommandsError> {
772    let (_pkg_json, items) = self
773      .resolve_npm_binary_commands_for_package_with_pkg_json(package_folder)?;
774    Ok(items)
775  }
776
777  fn resolve_npm_binary_commands_for_package_with_pkg_json(
778    &self,
779    package_folder: &Path,
780  ) -> Result<
781    (PackageJsonRc, BTreeMap<String, PathBuf>),
782    ResolvePkgNpmBinaryCommandsError,
783  > {
784    let pkg_json_path = package_folder.join("package.json");
785    let Some(package_json) =
786      self.pkg_json_resolver.load_package_json(&pkg_json_path)?
787    else {
788      return Err(ResolvePkgNpmBinaryCommandsError::MissingPkgJson(
789        MissingPkgJsonError { pkg_json_path },
790      ));
791    };
792    let bins = package_json.resolve_bins()?;
793    // TODO(bartlomieju): skipped checking errors for commonJS resolution and
794    // "preserveSymlinksMain"/"preserveSymlinks" options.
795    let items = match bins {
796      deno_package_json::PackageJsonBins::Directory(path_buf) => {
797        self.resolve_npm_commands_from_bin_dir(&path_buf)
798      }
799      deno_package_json::PackageJsonBins::Bins(items) => items,
800    };
801    Ok((package_json, items))
802  }
803
804  pub fn resolve_npm_commands_from_bin_dir(
805    &self,
806    bin_dir: &Path,
807  ) -> BTreeMap<String, PathBuf> {
808    log::debug!("Resolving npm commands in '{}'.", bin_dir.display());
809    let mut result = BTreeMap::new();
810    match self.sys.fs_read_dir(bin_dir) {
811      Ok(entries) => {
812        for entry in entries {
813          let Ok(entry) = entry else {
814            continue;
815          };
816          if let Some((command, path)) =
817            self.resolve_bin_dir_entry_command(entry)
818          {
819            result.insert(command, path);
820          }
821        }
822      }
823      Err(err) => {
824        log::debug!("Failed read_dir for '{}': {:#}", bin_dir.display(), err);
825      }
826    }
827    result
828  }
829
830  fn resolve_bin_dir_entry_command(
831    &self,
832    entry: TSys::ReadDirEntry,
833  ) -> Option<(String, PathBuf)> {
834    if entry.path().extension().is_some() {
835      return None; // only look at files without extensions (even on Windows)
836    }
837    let file_type = entry.file_type().ok()?;
838    let path = if file_type.is_file() {
839      entry.path()
840    } else if file_type.is_symlink() {
841      Cow::Owned(self.sys.fs_canonicalize(entry.path()).ok()?)
842    } else {
843      return None;
844    };
845    let text = self.sys.fs_read_to_string_lossy(&path).ok()?;
846    let command_name = entry.file_name().to_string_lossy().into_owned();
847    if let Some(path) = resolve_execution_path_from_npx_shim(path, &text) {
848      log::debug!(
849        "Resolved npx command '{}' to '{}'.",
850        command_name,
851        path.display()
852      );
853      Some((command_name, path))
854    } else {
855      log::debug!("Failed resolving npx command '{}'.", command_name);
856      None
857    }
858  }
859
860  /// Resolves an npm package folder path from the specified referrer.
861  pub fn resolve_package_folder_from_package(
862    &self,
863    specifier: &str,
864    referrer: &UrlOrPathRef,
865  ) -> Result<PathBuf, errors::PackageFolderResolveError> {
866    self
867      .npm_pkg_folder_resolver
868      .resolve_package_folder_from_package(specifier, referrer)
869  }
870
871  fn maybe_resolve_types(
872    &self,
873    url: LocalUrlOrPath,
874    maybe_referrer: Option<&UrlOrPathRef>,
875    resolution_mode: ResolutionMode,
876    conditions: &[Cow<'static, str>],
877    resolution_kind: NodeResolutionKind,
878  ) -> Result<MaybeTypesResolvedUrl, TypesNotFoundError> {
879    if resolution_kind.is_types() {
880      let file_path = match url {
881        LocalUrlOrPath::Url(url) => {
882          match deno_path_util::url_to_file_path(&url) {
883            Ok(path) => LocalPath {
884              path,
885              known_exists: false,
886            },
887            Err(_) => {
888              return Ok(MaybeTypesResolvedUrl(LocalUrlOrPath::Url(url)));
889            }
890          }
891        }
892        LocalUrlOrPath::Path(path) => path,
893      };
894      self.path_to_declaration_path(
895        file_path,
896        maybe_referrer,
897        resolution_mode,
898        conditions,
899      )
900    } else {
901      Ok(MaybeTypesResolvedUrl(url))
902    }
903  }
904
905  /// Checks if the resolved file has a corresponding declaration file.
906  fn path_to_declaration_path(
907    &self,
908    local_path: LocalPath,
909    maybe_referrer: Option<&UrlOrPathRef>,
910    resolution_mode: ResolutionMode,
911    conditions: &[Cow<'static, str>],
912  ) -> Result<MaybeTypesResolvedUrl, TypesNotFoundError> {
913    fn probe_extensions<TSys: FsMetadata>(
914      sys: &NodeResolutionSys<TSys>,
915      path: &Path,
916      media_type: MediaType,
917      resolution_mode: ResolutionMode,
918    ) -> Option<PathBuf> {
919      let mut searched_for_d_mts = false;
920      let mut searched_for_d_cts = false;
921      if media_type == MediaType::Mjs {
922        let d_mts_path = with_known_extension(path, "d.mts");
923        if sys.exists_(&d_mts_path) {
924          return Some(d_mts_path);
925        }
926        searched_for_d_mts = true;
927      } else if media_type == MediaType::Cjs {
928        let d_cts_path = with_known_extension(path, "d.cts");
929        if sys.exists_(&d_cts_path) {
930          return Some(d_cts_path);
931        }
932        searched_for_d_cts = true;
933      }
934
935      let dts_path = with_known_extension(path, "d.ts");
936      if sys.exists_(&dts_path) {
937        return Some(dts_path);
938      }
939
940      let specific_dts_path = match resolution_mode {
941        ResolutionMode::Require if !searched_for_d_cts => {
942          Some(with_known_extension(path, "d.cts"))
943        }
944        ResolutionMode::Import if !searched_for_d_mts => {
945          Some(with_known_extension(path, "d.mts"))
946        }
947        _ => None, // already searched above
948      };
949      if let Some(specific_dts_path) = specific_dts_path
950        && sys.exists_(&specific_dts_path)
951      {
952        return Some(specific_dts_path);
953      }
954      let ts_path = with_known_extension(path, "ts");
955      if sys.is_file(&ts_path) {
956        return Some(ts_path);
957      }
958      None
959    }
960
961    let media_type = MediaType::from_path(&local_path.path);
962    if media_type.is_declaration() {
963      return Ok(MaybeTypesResolvedUrl(LocalUrlOrPath::Path(local_path)));
964    }
965    if let Some(path) =
966      probe_extensions(&self.sys, &local_path.path, media_type, resolution_mode)
967    {
968      return Ok(MaybeTypesResolvedUrl(LocalUrlOrPath::Path(LocalPath {
969        path,
970        known_exists: true,
971      })));
972    }
973    if self.sys.is_dir(&local_path.path) {
974      let resolution_result = self.resolve_package_dir_subpath(
975        &local_path.path,
976        /* sub path */ ".",
977        maybe_referrer,
978        resolution_mode,
979        conditions,
980        NodeResolutionKind::Types,
981      );
982      if let Ok((url_or_path, _)) = resolution_result {
983        return Ok(url_or_path);
984      }
985      let index_path = local_path.path.join("index.js");
986      if let Some(path) = probe_extensions(
987        &self.sys,
988        &index_path,
989        MediaType::from_path(&index_path),
990        resolution_mode,
991      ) {
992        return Ok(MaybeTypesResolvedUrl(LocalUrlOrPath::Path(LocalPath {
993          path,
994          known_exists: true,
995        })));
996      }
997    }
998    // allow resolving .ts-like or .css files for types resolution
999    if media_type.is_typed() || media_type == MediaType::Css {
1000      return Ok(MaybeTypesResolvedUrl(LocalUrlOrPath::Path(local_path)));
1001    }
1002    Err(TypesNotFoundError(Box::new(TypesNotFoundErrorData {
1003      code_specifier: UrlOrPathRef::from_path(&local_path.path).display(),
1004      maybe_referrer: maybe_referrer.map(|r| r.display()),
1005    })))
1006  }
1007
1008  #[allow(clippy::too_many_arguments)]
1009  pub fn resolve_package_import(
1010    &self,
1011    name: &str,
1012    maybe_referrer: Option<&UrlOrPathRef>,
1013    referrer_pkg_json: Option<&PackageJson>,
1014    resolution_mode: ResolutionMode,
1015    resolution_kind: NodeResolutionKind,
1016  ) -> Result<UrlOrPath, PackageImportsResolveError> {
1017    self
1018      .package_imports_resolve_internal(
1019        name,
1020        maybe_referrer,
1021        resolution_mode,
1022        referrer_pkg_json,
1023        self.condition_resolver.resolve(resolution_mode),
1024        resolution_kind,
1025      )
1026      .map(|url| url.0.into_url_or_path())
1027  }
1028
1029  #[allow(clippy::too_many_arguments)]
1030  fn package_imports_resolve_internal(
1031    &self,
1032    name: &str,
1033    maybe_referrer: Option<&UrlOrPathRef>,
1034    resolution_mode: ResolutionMode,
1035    referrer_pkg_json: Option<&PackageJson>,
1036    conditions: &[Cow<'static, str>],
1037    resolution_kind: NodeResolutionKind,
1038  ) -> Result<MaybeTypesResolvedUrl, PackageImportsResolveError> {
1039    if name == "#" || name.starts_with("#/") || name.ends_with('/') {
1040      let reason = "is not a valid internal imports specifier name";
1041      return Err(
1042        errors::InvalidModuleSpecifierError {
1043          request: name.to_string(),
1044          reason: Cow::Borrowed(reason),
1045          maybe_referrer: maybe_referrer.map(to_specifier_display_string),
1046        }
1047        .into(),
1048      );
1049    }
1050
1051    if let Some(pkg_json) = &referrer_pkg_json
1052      && let Some(resolved_import) = resolve_pkg_json_import(pkg_json, name)
1053    {
1054      let maybe_resolved = self.resolve_package_target(
1055        &pkg_json.path,
1056        resolved_import.target,
1057        resolved_import.sub_path,
1058        resolved_import.package_sub_path,
1059        maybe_referrer,
1060        resolution_mode,
1061        resolved_import.is_pattern,
1062        true,
1063        conditions,
1064        resolution_kind,
1065      )?;
1066      if let Some(resolved) = maybe_resolved {
1067        return Ok(resolved);
1068      }
1069    }
1070
1071    Err(
1072      PackageImportNotDefinedError {
1073        name: name.to_string(),
1074        package_json_path: referrer_pkg_json.map(|p| p.path.clone()),
1075        maybe_referrer: maybe_referrer.map(|r| r.display()),
1076      }
1077      .into(),
1078    )
1079  }
1080
1081  #[allow(clippy::too_many_arguments)]
1082  fn resolve_package_target_string(
1083    &self,
1084    target: &str,
1085    subpath: &str,
1086    match_: &str,
1087    package_json_path: &Path,
1088    maybe_referrer: Option<&UrlOrPathRef>,
1089    resolution_mode: ResolutionMode,
1090    pattern: bool,
1091    internal: bool,
1092    conditions: &[Cow<'static, str>],
1093    resolution_kind: NodeResolutionKind,
1094  ) -> Result<MaybeTypesResolvedUrl, PackageTargetResolveError> {
1095    if !subpath.is_empty() && !pattern && !target.ends_with('/') {
1096      return Err(
1097        InvalidPackageTargetError {
1098          pkg_json_path: package_json_path.to_path_buf(),
1099          sub_path: match_.to_string(),
1100          target: target.to_string(),
1101          is_import: internal,
1102          maybe_referrer: maybe_referrer.map(|r| r.display()),
1103        }
1104        .into(),
1105      );
1106    }
1107    let invalid_segment_re =
1108      lazy_regex::regex!(r"(^|\\|/)(\.\.?|node_modules)(\\|/|$)");
1109    let pattern_re = lazy_regex::regex!(r"\*");
1110    if !target.starts_with("./") {
1111      if internal && !target.starts_with("../") && !target.starts_with('/') {
1112        let target_url = Url::parse(target);
1113        match target_url {
1114          Ok(url) => {
1115            if self
1116              .get_module_name_from_builtin_node_module_url(&url)?
1117              .is_some()
1118            {
1119              return Ok(MaybeTypesResolvedUrl(LocalUrlOrPath::Url(url)));
1120            }
1121          }
1122          Err(_) => {
1123            let export_target = if pattern {
1124              pattern_re.replace(target, |_caps: &regex::Captures| subpath)
1125            } else if subpath.is_empty() {
1126              Cow::Borrowed(target)
1127            } else {
1128              Cow::Owned(format!("{target}{subpath}"))
1129            };
1130            let result = match self.package_resolve(
1131              &export_target,
1132              &UrlOrPathRef::from_path(package_json_path),
1133              resolution_mode,
1134              conditions,
1135              resolution_kind,
1136            ) {
1137              Ok((url, _)) => Ok(url),
1138              Err(err) => match err.code() {
1139                NodeJsErrorCode::ERR_INVALID_FILE_URL_PATH
1140                | NodeJsErrorCode::ERR_INVALID_MODULE_SPECIFIER
1141                | NodeJsErrorCode::ERR_INVALID_PACKAGE_CONFIG
1142                | NodeJsErrorCode::ERR_INVALID_PACKAGE_TARGET
1143                | NodeJsErrorCode::ERR_PACKAGE_IMPORT_NOT_DEFINED
1144                | NodeJsErrorCode::ERR_PACKAGE_PATH_NOT_EXPORTED
1145                | NodeJsErrorCode::ERR_UNKNOWN_FILE_EXTENSION
1146                | NodeJsErrorCode::ERR_UNSUPPORTED_DIR_IMPORT
1147                | NodeJsErrorCode::ERR_UNSUPPORTED_ESM_URL_SCHEME
1148                | NodeJsErrorCode::ERR_UNKNOWN_BUILTIN_MODULE
1149                | NodeJsErrorCode::ERR_TYPES_NOT_FOUND => {
1150                  Err(PackageTargetResolveErrorKind::PackageResolve(err).into())
1151                }
1152                NodeJsErrorCode::ERR_MODULE_NOT_FOUND => Err(
1153                  PackageTargetResolveErrorKind::NotFound(
1154                    PackageTargetNotFoundError {
1155                      pkg_json_path: package_json_path.to_path_buf(),
1156                      target: export_target.to_string(),
1157                      maybe_resolved: err
1158                        .maybe_specifier()
1159                        .map(|c| c.into_owned()),
1160                      maybe_referrer: maybe_referrer.map(|r| r.display()),
1161                      resolution_mode,
1162                      resolution_kind,
1163                    },
1164                  )
1165                  .into(),
1166                ),
1167              },
1168            };
1169
1170            return match result {
1171              Ok(url) => Ok(url),
1172              Err(err) => {
1173                if self
1174                  .is_built_in_node_module_checker
1175                  .is_builtin_node_module(target)
1176                {
1177                  Ok(MaybeTypesResolvedUrl(LocalUrlOrPath::Url(
1178                    Url::parse(&format!("node:{}", target)).unwrap(),
1179                  )))
1180                } else {
1181                  Err(err)
1182                }
1183              }
1184            };
1185          }
1186        }
1187      }
1188      return Err(
1189        InvalidPackageTargetError {
1190          pkg_json_path: package_json_path.to_path_buf(),
1191          sub_path: match_.to_string(),
1192          target: target.to_string(),
1193          is_import: internal,
1194          maybe_referrer: maybe_referrer.map(|r| r.display()),
1195        }
1196        .into(),
1197      );
1198    }
1199    if invalid_segment_re.is_match(&target[2..]) {
1200      return Err(
1201        InvalidPackageTargetError {
1202          pkg_json_path: package_json_path.to_path_buf(),
1203          sub_path: match_.to_string(),
1204          target: target.to_string(),
1205          is_import: internal,
1206          maybe_referrer: maybe_referrer.map(|r| r.display()),
1207        }
1208        .into(),
1209      );
1210    }
1211    let package_path = package_json_path.parent().unwrap();
1212    let resolved_path = package_path.join(target).clean();
1213    if !resolved_path.starts_with(package_path) {
1214      return Err(
1215        InvalidPackageTargetError {
1216          pkg_json_path: package_json_path.to_path_buf(),
1217          sub_path: match_.to_string(),
1218          target: target.to_string(),
1219          is_import: internal,
1220          maybe_referrer: maybe_referrer.map(|r| r.display()),
1221        }
1222        .into(),
1223      );
1224    }
1225    let path = if subpath.is_empty() {
1226      LocalPath {
1227        path: resolved_path,
1228        known_exists: false,
1229      }
1230    } else if invalid_segment_re.is_match(subpath) {
1231      let request = if pattern {
1232        match_.replace('*', subpath)
1233      } else {
1234        format!("{match_}{subpath}")
1235      };
1236      return Err(
1237        throw_invalid_subpath(
1238          request,
1239          package_json_path,
1240          internal,
1241          maybe_referrer,
1242        )
1243        .into(),
1244      );
1245    } else if pattern {
1246      let resolved_path_str = resolved_path.to_string_lossy();
1247      let replaced = pattern_re
1248        .replace(&resolved_path_str, |_caps: &regex::Captures| subpath);
1249      LocalPath {
1250        path: PathBuf::from(replaced.as_ref()),
1251        known_exists: false,
1252      }
1253    } else {
1254      LocalPath {
1255        path: resolved_path.join(subpath).clean(),
1256        known_exists: false,
1257      }
1258    };
1259    Ok(self.maybe_resolve_types(
1260      LocalUrlOrPath::Path(path),
1261      maybe_referrer,
1262      resolution_mode,
1263      conditions,
1264      resolution_kind,
1265    )?)
1266  }
1267
1268  #[allow(clippy::too_many_arguments)]
1269  fn resolve_package_target(
1270    &self,
1271    package_json_path: &Path,
1272    target: &Value,
1273    subpath: &str,
1274    package_subpath: &str,
1275    maybe_referrer: Option<&UrlOrPathRef>,
1276    resolution_mode: ResolutionMode,
1277    pattern: bool,
1278    internal: bool,
1279    conditions: &[Cow<'static, str>],
1280    resolution_kind: NodeResolutionKind,
1281  ) -> Result<Option<MaybeTypesResolvedUrl>, PackageTargetResolveError> {
1282    let result = self.resolve_package_target_inner(
1283      package_json_path,
1284      target,
1285      subpath,
1286      package_subpath,
1287      maybe_referrer,
1288      resolution_mode,
1289      pattern,
1290      internal,
1291      conditions,
1292      resolution_kind,
1293    );
1294    match result {
1295      Ok(maybe_resolved) => Ok(maybe_resolved),
1296      Err(err) => {
1297        if resolution_kind.is_types()
1298          && err.code() == NodeJsErrorCode::ERR_TYPES_NOT_FOUND
1299          && conditions != TYPES_ONLY_CONDITIONS
1300        {
1301          // try resolving with just "types" conditions for when someone misconfigures
1302          // and puts the "types" condition in the wrong place
1303          if let Ok(Some(resolved)) = self.resolve_package_target_inner(
1304            package_json_path,
1305            target,
1306            subpath,
1307            package_subpath,
1308            maybe_referrer,
1309            resolution_mode,
1310            pattern,
1311            internal,
1312            TYPES_ONLY_CONDITIONS,
1313            resolution_kind,
1314          ) {
1315            return Ok(Some(resolved));
1316          }
1317        }
1318
1319        Err(err)
1320      }
1321    }
1322  }
1323
1324  #[allow(clippy::too_many_arguments)]
1325  fn resolve_package_target_inner(
1326    &self,
1327    package_json_path: &Path,
1328    target: &Value,
1329    subpath: &str,
1330    package_subpath: &str,
1331    maybe_referrer: Option<&UrlOrPathRef>,
1332    resolution_mode: ResolutionMode,
1333    pattern: bool,
1334    internal: bool,
1335    conditions: &[Cow<'static, str>],
1336    resolution_kind: NodeResolutionKind,
1337  ) -> Result<Option<MaybeTypesResolvedUrl>, PackageTargetResolveError> {
1338    if let Some(target) = target.as_str() {
1339      let url_or_path = self.resolve_package_target_string(
1340        target,
1341        subpath,
1342        package_subpath,
1343        package_json_path,
1344        maybe_referrer,
1345        resolution_mode,
1346        pattern,
1347        internal,
1348        conditions,
1349        resolution_kind,
1350      )?;
1351      return Ok(Some(url_or_path));
1352    } else if let Some(target_arr) = target.as_array() {
1353      if target_arr.is_empty() {
1354        return Ok(None);
1355      }
1356
1357      let mut last_error = None;
1358      for target_item in target_arr {
1359        let resolved_result = self.resolve_package_target(
1360          package_json_path,
1361          target_item,
1362          subpath,
1363          package_subpath,
1364          maybe_referrer,
1365          resolution_mode,
1366          pattern,
1367          internal,
1368          conditions,
1369          resolution_kind,
1370        );
1371
1372        match resolved_result {
1373          Ok(Some(resolved)) => return Ok(Some(resolved)),
1374          Ok(None) => {
1375            last_error = None;
1376            continue;
1377          }
1378          Err(e) => {
1379            if e.code() == NodeJsErrorCode::ERR_INVALID_PACKAGE_TARGET {
1380              last_error = Some(e);
1381              continue;
1382            } else {
1383              return Err(e);
1384            }
1385          }
1386        }
1387      }
1388      if last_error.is_none() {
1389        return Ok(None);
1390      }
1391      return Err(last_error.unwrap());
1392    } else if let Some(target_obj) = target.as_object() {
1393      for (key, condition_target) in target_obj {
1394        // TODO(bartlomieju): verify that keys are not numeric
1395        // return Err(errors::err_invalid_package_config(
1396        //   to_file_path_string(package_json_url),
1397        //   Some(base.as_str().to_string()),
1398        //   Some("\"exports\" cannot contain numeric property keys.".to_string()),
1399        // ));
1400
1401        if key == "default"
1402          || conditions.contains(&Cow::Borrowed(key))
1403          || resolution_kind.is_types() && self.matches_types_key(key)
1404        {
1405          let resolved = self.resolve_package_target(
1406            package_json_path,
1407            condition_target,
1408            subpath,
1409            package_subpath,
1410            maybe_referrer,
1411            resolution_mode,
1412            pattern,
1413            internal,
1414            conditions,
1415            resolution_kind,
1416          )?;
1417          match resolved {
1418            Some(resolved) => return Ok(Some(resolved)),
1419            None => {
1420              continue;
1421            }
1422          }
1423        }
1424      }
1425    }
1426
1427    Ok(None)
1428  }
1429
1430  fn matches_types_key(&self, key: &str) -> bool {
1431    if key == "types" {
1432      return true;
1433    }
1434    let Some(ts_version) = &self.resolution_config.typescript_version else {
1435      return false;
1436    };
1437    let Some(constraint) = key.strip_prefix("types@") else {
1438      return false;
1439    };
1440    let Ok(version_req) = VersionReq::parse_from_npm(constraint) else {
1441      return false;
1442    };
1443    version_req.matches(ts_version)
1444  }
1445
1446  #[allow(clippy::too_many_arguments)]
1447  pub fn package_exports_resolve(
1448    &self,
1449    package_json_path: &Path,
1450    package_subpath: &str,
1451    package_exports: &Map<String, Value>,
1452    maybe_referrer: Option<&UrlOrPathRef>,
1453    resolution_mode: ResolutionMode,
1454    conditions: &[Cow<'static, str>],
1455    resolution_kind: NodeResolutionKind,
1456  ) -> Result<UrlOrPath, PackageExportsResolveError> {
1457    self
1458      .package_exports_resolve_internal(
1459        package_json_path,
1460        package_subpath,
1461        package_exports,
1462        maybe_referrer,
1463        resolution_mode,
1464        conditions,
1465        resolution_kind,
1466      )
1467      .map(|url| url.0.into_url_or_path())
1468  }
1469
1470  #[allow(clippy::too_many_arguments)]
1471  fn package_exports_resolve_internal(
1472    &self,
1473    package_json_path: &Path,
1474    package_subpath: &str,
1475    package_exports: &Map<String, Value>,
1476    maybe_referrer: Option<&UrlOrPathRef>,
1477    resolution_mode: ResolutionMode,
1478    conditions: &[Cow<'static, str>],
1479    resolution_kind: NodeResolutionKind,
1480  ) -> Result<MaybeTypesResolvedUrl, PackageExportsResolveError> {
1481    if let Some(target) = package_exports.get(package_subpath)
1482      && package_subpath.find('*').is_none()
1483      && !package_subpath.ends_with('/')
1484    {
1485      let resolved = self.resolve_package_target(
1486        package_json_path,
1487        target,
1488        "",
1489        package_subpath,
1490        maybe_referrer,
1491        resolution_mode,
1492        false,
1493        false,
1494        conditions,
1495        resolution_kind,
1496      )?;
1497      return match resolved {
1498        Some(resolved) => Ok(resolved),
1499        None => Err(
1500          PackagePathNotExportedError {
1501            pkg_json_path: package_json_path.to_path_buf(),
1502            subpath: package_subpath.to_string(),
1503            maybe_referrer: maybe_referrer.map(|r| r.display()),
1504            resolution_kind,
1505          }
1506          .into(),
1507        ),
1508      };
1509    }
1510
1511    let mut best_match = "";
1512    let mut best_match_data = None;
1513    for (key, target) in package_exports {
1514      let Some(pattern_index) = key.find('*') else {
1515        continue;
1516      };
1517      let key_sub = &key[0..pattern_index];
1518      if !package_subpath.starts_with(key_sub) {
1519        continue;
1520      }
1521
1522      // When this reaches EOL, this can throw at the top of the whole function:
1523      //
1524      // if (StringPrototypeEndsWith(packageSubpath, '/'))
1525      //   throwInvalidSubpath(packageSubpath)
1526      //
1527      // To match "imports" and the spec.
1528      if package_subpath.ends_with('/') {
1529        // TODO(bartlomieju):
1530        // emitTrailingSlashPatternDeprecation();
1531      }
1532      let pattern_trailer = &key[pattern_index + 1..];
1533      if package_subpath.len() >= key.len()
1534        && package_subpath.ends_with(&pattern_trailer)
1535        && pattern_key_compare(best_match, key) == 1
1536        && key.rfind('*') == Some(pattern_index)
1537      {
1538        best_match = key;
1539        best_match_data = Some((
1540          target,
1541          &package_subpath
1542            [pattern_index..(package_subpath.len() - pattern_trailer.len())],
1543        ));
1544      }
1545    }
1546
1547    if let Some((target, subpath)) = best_match_data {
1548      let maybe_resolved = self.resolve_package_target(
1549        package_json_path,
1550        target,
1551        subpath,
1552        best_match,
1553        maybe_referrer,
1554        resolution_mode,
1555        true,
1556        false,
1557        conditions,
1558        resolution_kind,
1559      )?;
1560      if let Some(resolved) = maybe_resolved {
1561        return Ok(resolved);
1562      } else {
1563        return Err(
1564          PackagePathNotExportedError {
1565            pkg_json_path: package_json_path.to_path_buf(),
1566            subpath: package_subpath.to_string(),
1567            maybe_referrer: maybe_referrer.map(|r| r.display()),
1568            resolution_kind,
1569          }
1570          .into(),
1571        );
1572      }
1573    }
1574
1575    Err(
1576      PackagePathNotExportedError {
1577        pkg_json_path: package_json_path.to_path_buf(),
1578        subpath: package_subpath.to_string(),
1579        maybe_referrer: maybe_referrer.map(|r| r.display()),
1580        resolution_kind,
1581      }
1582      .into(),
1583    )
1584  }
1585
1586  fn package_resolve(
1587    &self,
1588    specifier: &str,
1589    referrer: &UrlOrPathRef,
1590    resolution_mode: ResolutionMode,
1591    conditions: &[Cow<'static, str>],
1592    resolution_kind: NodeResolutionKind,
1593  ) -> Result<(MaybeTypesResolvedUrl, ResolvedMethod), PackageResolveError> {
1594    let (package_name, package_subpath, _is_scoped) =
1595      parse_npm_pkg_name(specifier, referrer)?;
1596
1597    if let Some(package_config) = self
1598      .pkg_json_resolver
1599      .get_closest_package_json(referrer.path()?)?
1600    {
1601      // ResolveSelf
1602      if package_config.name.as_deref() == Some(package_name)
1603        && let Some(exports) = &package_config.exports
1604      {
1605        return self
1606          .package_exports_resolve_internal(
1607            &package_config.path,
1608            &package_subpath,
1609            exports,
1610            Some(referrer),
1611            resolution_mode,
1612            conditions,
1613            resolution_kind,
1614          )
1615          .map(|url| (url, ResolvedMethod::PackageExports))
1616          .map_err(|err| err.into());
1617      }
1618    }
1619
1620    self.resolve_package_subpath_for_package(
1621      package_name,
1622      &package_subpath,
1623      referrer,
1624      resolution_mode,
1625      conditions,
1626      resolution_kind,
1627    )
1628  }
1629
1630  #[allow(clippy::too_many_arguments)]
1631  fn resolve_package_subpath_for_package(
1632    &self,
1633    package_name: &str,
1634    package_subpath: &str,
1635    referrer: &UrlOrPathRef,
1636    resolution_mode: ResolutionMode,
1637    conditions: &[Cow<'static, str>],
1638    resolution_kind: NodeResolutionKind,
1639  ) -> Result<(MaybeTypesResolvedUrl, ResolvedMethod), PackageResolveError> {
1640    let result = self.resolve_package_subpath_for_package_inner(
1641      package_name,
1642      package_subpath,
1643      referrer,
1644      resolution_mode,
1645      conditions,
1646      resolution_kind,
1647    );
1648    if resolution_kind.is_types() && result.is_err() {
1649      // try to resolve with the @types package
1650      let package_name = types_package_name(package_name);
1651      if let Ok(result) = self.resolve_package_subpath_for_package_inner(
1652        &package_name,
1653        package_subpath,
1654        referrer,
1655        resolution_mode,
1656        conditions,
1657        resolution_kind,
1658      ) {
1659        return Ok(result);
1660      }
1661    }
1662    result
1663  }
1664
1665  #[allow(clippy::too_many_arguments)]
1666  fn resolve_package_subpath_for_package_inner(
1667    &self,
1668    package_name: &str,
1669    package_subpath: &str,
1670    referrer: &UrlOrPathRef,
1671    resolution_mode: ResolutionMode,
1672    conditions: &[Cow<'static, str>],
1673    resolution_kind: NodeResolutionKind,
1674  ) -> Result<(MaybeTypesResolvedUrl, ResolvedMethod), PackageResolveError> {
1675    let package_dir_path = self
1676      .npm_pkg_folder_resolver
1677      .resolve_package_folder_from_package(package_name, referrer)?;
1678
1679    // todo: error with this instead when can't find package
1680    // Err(errors::err_module_not_found(
1681    //   &package_json_url
1682    //     .join(".")
1683    //     .unwrap()
1684    //     .to_file_path()
1685    //     .unwrap()
1686    //     .display()
1687    //     .to_string(),
1688    //   &to_file_path_string(referrer),
1689    //   "package",
1690    // ))
1691
1692    // Package match.
1693    self
1694      .resolve_package_dir_subpath(
1695        &package_dir_path,
1696        package_subpath,
1697        Some(referrer),
1698        resolution_mode,
1699        conditions,
1700        resolution_kind,
1701      )
1702      .map_err(|err| err.into())
1703  }
1704
1705  #[allow(clippy::too_many_arguments)]
1706  fn resolve_package_dir_subpath(
1707    &self,
1708    package_dir_path: &Path,
1709    package_subpath: &str,
1710    maybe_referrer: Option<&UrlOrPathRef>,
1711    resolution_mode: ResolutionMode,
1712    conditions: &[Cow<'static, str>],
1713    resolution_kind: NodeResolutionKind,
1714  ) -> Result<(MaybeTypesResolvedUrl, ResolvedMethod), PackageSubpathResolveError>
1715  {
1716    let package_json_path = package_dir_path.join("package.json");
1717    match self
1718      .pkg_json_resolver
1719      .load_package_json(&package_json_path)?
1720    {
1721      Some(pkg_json) => self.resolve_package_subpath(
1722        &pkg_json,
1723        package_subpath,
1724        maybe_referrer,
1725        resolution_mode,
1726        conditions,
1727        resolution_kind,
1728      ),
1729      None => self
1730        .resolve_package_subpath_no_pkg_json(
1731          package_dir_path,
1732          package_subpath,
1733          maybe_referrer,
1734          resolution_mode,
1735          conditions,
1736          resolution_kind,
1737        )
1738        .map(|url| (url, ResolvedMethod::PackageSubPath))
1739        .map_err(|err| {
1740          PackageSubpathResolveErrorKind::LegacyResolve(err).into()
1741        }),
1742    }
1743  }
1744
1745  #[allow(clippy::too_many_arguments)]
1746  fn resolve_package_subpath(
1747    &self,
1748    package_json: &PackageJson,
1749    package_subpath: &str,
1750    referrer: Option<&UrlOrPathRef>,
1751    resolution_mode: ResolutionMode,
1752    conditions: &[Cow<'static, str>],
1753    resolution_kind: NodeResolutionKind,
1754  ) -> Result<(MaybeTypesResolvedUrl, ResolvedMethod), PackageSubpathResolveError>
1755  {
1756    if let Some(exports) = &package_json.exports {
1757      let result = self.package_exports_resolve_internal(
1758        &package_json.path,
1759        package_subpath,
1760        exports,
1761        referrer,
1762        resolution_mode,
1763        conditions,
1764        resolution_kind,
1765      );
1766      match result {
1767        Ok(found) => return Ok((found, ResolvedMethod::PackageExports)),
1768        Err(exports_err) => {
1769          if resolution_kind.is_types() && package_subpath == "." {
1770            return self
1771              .legacy_main_resolve(
1772                package_json,
1773                referrer,
1774                resolution_mode,
1775                conditions,
1776                resolution_kind,
1777              )
1778              .map(|url| (url, ResolvedMethod::PackageSubPath))
1779              .map_err(|err| {
1780                PackageSubpathResolveErrorKind::LegacyResolve(err).into()
1781              });
1782          }
1783          return Err(
1784            PackageSubpathResolveErrorKind::Exports(exports_err).into(),
1785          );
1786        }
1787      }
1788    }
1789
1790    if package_subpath == "." {
1791      self
1792        .legacy_main_resolve(
1793          package_json,
1794          referrer,
1795          resolution_mode,
1796          conditions,
1797          resolution_kind,
1798        )
1799        .map(|url| (url, ResolvedMethod::PackageSubPath))
1800        .map_err(|err| {
1801          PackageSubpathResolveErrorKind::LegacyResolve(err).into_box()
1802        })
1803    } else {
1804      self
1805        .resolve_subpath_exact(
1806          package_json.path.parent().unwrap(),
1807          package_subpath,
1808          Some(package_json),
1809          referrer,
1810          resolution_mode,
1811          conditions,
1812          resolution_kind,
1813        )
1814        .map(|url| (url, ResolvedMethod::PackageSubPath))
1815        .map_err(|err| {
1816          PackageSubpathResolveErrorKind::LegacyResolve(err.into()).into_box()
1817        })
1818    }
1819  }
1820
1821  fn pkg_json_types_versions<'a>(
1822    &'a self,
1823    pkg_json: &'a PackageJson,
1824    resolution_kind: NodeResolutionKind,
1825  ) -> Option<TypesVersions<'a, TSys>> {
1826    if !resolution_kind.is_types() {
1827      return None;
1828    }
1829    pkg_json
1830      .types_versions
1831      .as_ref()
1832      .and_then(|entries| {
1833        let ts_version = self.resolution_config.typescript_version.as_ref()?;
1834        entries
1835          .iter()
1836          .filter_map(|(k, v)| {
1837            let version_req = VersionReq::parse_from_npm(k).ok()?;
1838            version_req.matches(ts_version).then_some(v)
1839          })
1840          .next()
1841      })
1842      .and_then(|value| value.as_object())
1843      .map(|value| TypesVersions {
1844        value,
1845        dir_path: pkg_json.dir_path(),
1846        sys: &self.sys,
1847      })
1848  }
1849
1850  #[allow(clippy::too_many_arguments)]
1851  fn resolve_subpath_exact(
1852    &self,
1853    directory: &Path,
1854    package_subpath: &str,
1855    package_json: Option<&PackageJson>,
1856    referrer: Option<&UrlOrPathRef>,
1857    resolution_mode: ResolutionMode,
1858    conditions: &[Cow<'static, str>],
1859    resolution_kind: NodeResolutionKind,
1860  ) -> Result<MaybeTypesResolvedUrl, TypesNotFoundError> {
1861    assert_ne!(package_subpath, ".");
1862    let types_versions = package_json.and_then(|pkg_json| {
1863      self.pkg_json_types_versions(pkg_json, resolution_kind)
1864    });
1865    let package_subpath = types_versions
1866      .and_then(|v| v.map(package_subpath))
1867      .unwrap_or(Cow::Borrowed(package_subpath));
1868    let file_path = directory.join(package_subpath.as_ref());
1869    self.maybe_resolve_types(
1870      LocalUrlOrPath::Path(LocalPath {
1871        path: file_path,
1872        known_exists: false,
1873      }),
1874      referrer,
1875      resolution_mode,
1876      conditions,
1877      resolution_kind,
1878    )
1879  }
1880
1881  fn resolve_package_subpath_no_pkg_json(
1882    &self,
1883    directory: &Path,
1884    package_subpath: &str,
1885    maybe_referrer: Option<&UrlOrPathRef>,
1886    resolution_mode: ResolutionMode,
1887    conditions: &[Cow<'static, str>],
1888    resolution_kind: NodeResolutionKind,
1889  ) -> Result<MaybeTypesResolvedUrl, LegacyResolveError> {
1890    if package_subpath == "." {
1891      self.legacy_index_resolve(
1892        directory,
1893        maybe_referrer,
1894        resolution_mode,
1895        resolution_kind,
1896      )
1897    } else {
1898      self
1899        .resolve_subpath_exact(
1900          directory,
1901          package_subpath,
1902          None,
1903          maybe_referrer,
1904          resolution_mode,
1905          conditions,
1906          resolution_kind,
1907        )
1908        .map_err(|err| err.into())
1909    }
1910  }
1911
1912  pub(crate) fn legacy_fallback_resolve<'a>(
1913    &self,
1914    package_json: &'a PackageJson,
1915  ) -> Option<&'a str> {
1916    fn filter_empty(value: Option<&str>) -> Option<&str> {
1917      value.map(|v| v.trim()).filter(|v| !v.is_empty())
1918    }
1919    if self.resolution_config.bundle_mode {
1920      let maybe_browser = if self.resolution_config.prefer_browser_field {
1921        filter_empty(package_json.browser.as_deref())
1922      } else {
1923        None
1924      };
1925      maybe_browser
1926        .or(filter_empty(package_json.module.as_deref()))
1927        .or(filter_empty(package_json.main.as_deref()))
1928    } else {
1929      filter_empty(package_json.main.as_deref())
1930    }
1931  }
1932
1933  fn legacy_main_resolve(
1934    &self,
1935    package_json: &PackageJson,
1936    maybe_referrer: Option<&UrlOrPathRef>,
1937    resolution_mode: ResolutionMode,
1938    conditions: &[Cow<'static, str>],
1939    resolution_kind: NodeResolutionKind,
1940  ) -> Result<MaybeTypesResolvedUrl, LegacyResolveError> {
1941    let maybe_main = if resolution_kind.is_types() {
1942      match package_json.types.as_ref() {
1943        Some(types) => {
1944          let types_versions =
1945            self.pkg_json_types_versions(package_json, resolution_kind);
1946          Some(
1947            types_versions
1948              .and_then(|v| v.map(types.as_ref()))
1949              .unwrap_or(Cow::Borrowed(types.as_str())),
1950          )
1951        }
1952        None => {
1953          // fallback to checking the main entrypoint for
1954          // a corresponding declaration file
1955          if let Some(main) = self.legacy_fallback_resolve(package_json) {
1956            let main = package_json.path.parent().unwrap().join(main).clean();
1957            let decl_path_result = self.path_to_declaration_path(
1958              LocalPath {
1959                path: main,
1960                known_exists: false,
1961              },
1962              maybe_referrer,
1963              resolution_mode,
1964              conditions,
1965            );
1966            // don't surface errors, fallback to checking the index now
1967            if let Ok(url_or_path) = decl_path_result {
1968              return Ok(url_or_path);
1969            }
1970          }
1971          None
1972        }
1973      }
1974    } else {
1975      self
1976        .legacy_fallback_resolve(package_json)
1977        .map(Cow::Borrowed)
1978    };
1979
1980    if let Some(main) = maybe_main.as_deref() {
1981      let guess = package_json.path.parent().unwrap().join(main).clean();
1982      if self.sys.is_file(&guess) {
1983        return Ok(self.maybe_resolve_types(
1984          LocalUrlOrPath::Path(LocalPath {
1985            path: guess,
1986            known_exists: true,
1987          }),
1988          maybe_referrer,
1989          resolution_mode,
1990          conditions,
1991          resolution_kind,
1992        )?);
1993      }
1994
1995      // todo(dsherret): investigate exactly how node and typescript handles this
1996      let endings = if resolution_kind.is_types() {
1997        match resolution_mode {
1998          ResolutionMode::Require => {
1999            vec![".d.ts", ".d.cts", "/index.d.ts", "/index.d.cts"]
2000          }
2001          ResolutionMode::Import => vec![
2002            ".d.ts",
2003            ".d.mts",
2004            "/index.d.ts",
2005            "/index.d.mts",
2006            ".d.cts",
2007            "/index.d.cts",
2008          ],
2009        }
2010      } else {
2011        vec![".js", "/index.js"]
2012      };
2013      for ending in endings {
2014        let guess = package_json
2015          .path
2016          .parent()
2017          .unwrap()
2018          .join(format!("{main}{ending}"))
2019          .clean();
2020        if self.sys.is_file(&guess) {
2021          // TODO(bartlomieju): emitLegacyIndexDeprecation()
2022          return Ok(MaybeTypesResolvedUrl(LocalUrlOrPath::Path(LocalPath {
2023            path: guess,
2024            known_exists: true,
2025          })));
2026        }
2027      }
2028    }
2029
2030    self.legacy_index_resolve(
2031      package_json.path.parent().unwrap(),
2032      maybe_referrer,
2033      resolution_mode,
2034      resolution_kind,
2035    )
2036  }
2037
2038  fn legacy_index_resolve(
2039    &self,
2040    directory: &Path,
2041    maybe_referrer: Option<&UrlOrPathRef>,
2042    resolution_mode: ResolutionMode,
2043    resolution_kind: NodeResolutionKind,
2044  ) -> Result<MaybeTypesResolvedUrl, LegacyResolveError> {
2045    let index_file_names = if resolution_kind.is_types() {
2046      // todo(dsherret): investigate exactly how typescript does this
2047      match resolution_mode {
2048        ResolutionMode::Require => vec!["index.d.ts", "index.d.cts"],
2049        ResolutionMode::Import => {
2050          vec!["index.d.ts", "index.d.mts", "index.d.cts"]
2051        }
2052      }
2053    } else {
2054      // Node only resolves index.js and not index.cjs/mjs
2055      vec!["index.js"]
2056    };
2057    for index_file_name in index_file_names {
2058      let guess = directory.join(index_file_name).clean();
2059      if self.sys.is_file(&guess) {
2060        // TODO(bartlomieju): emitLegacyIndexDeprecation()
2061        return Ok(MaybeTypesResolvedUrl(LocalUrlOrPath::Path(LocalPath {
2062          path: guess,
2063          known_exists: true,
2064        })));
2065      }
2066    }
2067
2068    if resolution_kind.is_types() {
2069      Err(
2070        TypesNotFoundError(Box::new(TypesNotFoundErrorData {
2071          code_specifier: UrlOrPathRef::from_path(&directory.join("index.js"))
2072            .display(),
2073          maybe_referrer: maybe_referrer.map(|r| r.display()),
2074        }))
2075        .into(),
2076      )
2077    } else {
2078      Err(
2079        ModuleNotFoundError {
2080          specifier: UrlOrPath::Path(directory.join("index.js")),
2081          maybe_referrer: maybe_referrer.map(|r| r.display()),
2082          suggested_ext: None,
2083        }
2084        .into(),
2085      )
2086    }
2087  }
2088
2089  /// Resolves a specifier that is pointing into a node_modules folder by canonicalizing it.
2090  ///
2091  /// Returns `None` when the specifier is not in a node_modules folder.
2092  pub fn handle_if_in_node_modules(&self, specifier: &Url) -> Option<Url> {
2093    // skip canonicalizing if we definitely know it's unnecessary
2094    if specifier.scheme() == "file"
2095      && specifier.path().contains("/node_modules/")
2096    {
2097      // Specifiers in the node_modules directory are canonicalized
2098      // so canoncalize then check if it's in the node_modules directory.
2099      let specifier = resolve_specifier_into_node_modules(&self.sys, specifier);
2100      return Some(specifier);
2101    }
2102
2103    None
2104  }
2105
2106  /// Ex. returns `fs` for `node:fs`
2107  fn get_module_name_from_builtin_node_module_url<'url>(
2108    &self,
2109    url: &'url Url,
2110  ) -> Result<Option<&'url str>, UnknownBuiltInNodeModuleError> {
2111    if url.scheme() != "node" {
2112      return Ok(None);
2113    }
2114
2115    let module_name = url.path();
2116
2117    if !self
2118      .is_built_in_node_module_checker
2119      .is_builtin_node_module(module_name)
2120    {
2121      return Err(UnknownBuiltInNodeModuleError {
2122        module_name: module_name.to_string(),
2123      });
2124    }
2125    Ok(Some(module_name))
2126  }
2127}
2128
2129struct ResolvedPkgJsonImport<'a> {
2130  pub target: &'a serde_json::Value,
2131  pub sub_path: &'a str,
2132  pub package_sub_path: &'a str,
2133  pub is_pattern: bool,
2134}
2135
2136fn resolve_pkg_json_import<'a>(
2137  pkg_json: &'a PackageJson,
2138  name: &'a str,
2139) -> Option<ResolvedPkgJsonImport<'a>> {
2140  let imports = pkg_json.imports.as_ref()?;
2141  if let Some((name, target)) =
2142    imports.get_key_value(name).filter(|_| !name.contains('*'))
2143  {
2144    Some(ResolvedPkgJsonImport {
2145      target,
2146      sub_path: "",
2147      package_sub_path: name,
2148      is_pattern: false,
2149    })
2150  } else {
2151    let mut best_match: &'a str = "";
2152    let mut best_match_subpath: &'a str = "";
2153    for key in imports.keys() {
2154      let pattern_index = key.find('*');
2155      if let Some(pattern_index) = pattern_index {
2156        let key_sub = &key[0..pattern_index];
2157        if name.starts_with(key_sub) {
2158          let pattern_trailer = &key[pattern_index + 1..];
2159          if name.len() > key.len()
2160            && name.ends_with(&pattern_trailer)
2161            && pattern_key_compare(best_match, key) == 1
2162            && key.rfind('*') == Some(pattern_index)
2163          {
2164            best_match = key;
2165            best_match_subpath =
2166              &name[pattern_index..(name.len() - pattern_trailer.len())];
2167          }
2168        }
2169      }
2170    }
2171
2172    if !best_match.is_empty() {
2173      let target = imports.get(best_match).unwrap();
2174      Some(ResolvedPkgJsonImport {
2175        target,
2176        sub_path: best_match_subpath,
2177        package_sub_path: best_match,
2178        is_pattern: true,
2179      })
2180    } else {
2181      None
2182    }
2183  }
2184}
2185
2186/// This is not ideal, but it works ok because it allows us to bypass
2187/// the shebang and execute the script directly with Deno.
2188fn resolve_execution_path_from_npx_shim(
2189  file_path: Cow<Path>,
2190  text: &str,
2191) -> Option<PathBuf> {
2192  static SCRIPT_PATH_RE: Lazy<Regex> =
2193    lazy_regex::lazy_regex!(r#""\$basedir\/([^"]+)" "\$@""#);
2194
2195  let maybe_first_line = {
2196    let index = text.find("\n")?;
2197    Some(&text[0..index])
2198  };
2199
2200  if let Some(first_line) = maybe_first_line {
2201    // NOTE(bartlomieju): this is not perfect, but handle two most common scenarios
2202    // where Node is run without any args.
2203    if first_line == "#!/usr/bin/env node"
2204      || first_line == "#!/usr/bin/env -S node"
2205    {
2206      // launch this file itself because it's a JS file
2207      return Some(file_path.into_owned());
2208    }
2209  }
2210
2211  // Search for...
2212  // > "$basedir/../next/dist/bin/next" "$@"
2213  // ...which is what it will look like on Windows
2214  SCRIPT_PATH_RE
2215    .captures(text)
2216    .and_then(|c| c.get(1))
2217    .map(|relative_path| {
2218      file_path.parent().unwrap().join(relative_path.as_str())
2219    })
2220}
2221
2222fn resolve_bin_entry_value<'a>(
2223  package_json: &PackageJson,
2224  bins: &'a BTreeMap<String, PathBuf>,
2225  bin_name: Option<&str>,
2226) -> Result<&'a Path, AnyError> {
2227  if bins.is_empty() {
2228    bail!(
2229      "'{}' did not have a bin property with a string or non-empty object value",
2230      package_json.path.display()
2231    );
2232  }
2233  let default_bin = package_json.resolve_default_bin_name().ok();
2234  let searching_bin = bin_name.or(default_bin);
2235  match searching_bin.and_then(|bin_name| bins.get(bin_name)) {
2236    Some(e) => Ok(e),
2237    None => {
2238      if bins.len() > 1
2239        && let Some(first) = bins.values().next()
2240        && bins.values().all(|path| path == first)
2241      {
2242        return Ok(first);
2243      }
2244      if bin_name.is_none()
2245        && bins.len() == 1
2246        && let Some(first) = bins.values().next()
2247      {
2248        return Ok(first);
2249      }
2250      let default_bin = package_json.resolve_default_bin_name().ok();
2251      let prefix = package_json
2252        .name
2253        .as_ref()
2254        .map(|n| {
2255          let mut prefix = format!("npm:{}", n);
2256          if let Some(version) = &package_json.version {
2257            prefix.push('@');
2258            prefix.push_str(version);
2259          }
2260          prefix
2261        })
2262        .unwrap_or_default();
2263      let keys = bins
2264        .keys()
2265        .map(|k| {
2266          if prefix.is_empty() {
2267            format!(" * {k}")
2268          } else if Some(k.as_str()) == default_bin {
2269            format!(" * {prefix}")
2270          } else {
2271            format!(" * {prefix}/{k}")
2272          }
2273        })
2274        .collect::<Vec<_>>();
2275      bail!(
2276        "'{}' did not have a bin entry for '{}'{}",
2277        package_json.path.display(),
2278        searching_bin.unwrap_or("<unspecified>"),
2279        if keys.is_empty() {
2280          "".to_string()
2281        } else {
2282          format!("\n\nPossibilities:\n{}", keys.join("\n"))
2283        }
2284      )
2285    }
2286  }
2287}
2288
2289fn should_be_treated_as_relative_or_absolute_path(specifier: &str) -> bool {
2290  if specifier.is_empty() {
2291    return false;
2292  }
2293
2294  if specifier.starts_with('/') {
2295    return true;
2296  }
2297
2298  deno_path_util::is_relative_specifier(specifier)
2299}
2300
2301/// Alternate `PathBuf::with_extension` that will handle known extensions
2302/// more intelligently.
2303fn with_known_extension(path: &Path, ext: &str) -> PathBuf {
2304  const NON_DECL_EXTS: &[&str] = &[
2305    "cjs", "js", "json", "jsx", "mjs", "tsx", /* ex. types.d */ "d",
2306  ];
2307  const DECL_EXTS: &[&str] = &["cts", "mts", "ts"];
2308
2309  let file_name = match path.file_name() {
2310    Some(value) => value.to_string_lossy(),
2311    None => return path.to_path_buf(),
2312  };
2313  let lowercase_file_name = file_name.to_lowercase();
2314  let period_index = lowercase_file_name.rfind('.').and_then(|period_index| {
2315    let ext = &lowercase_file_name[period_index + 1..];
2316    if DECL_EXTS.contains(&ext) {
2317      if let Some(next_period_index) =
2318        lowercase_file_name[..period_index].rfind('.')
2319      {
2320        if &lowercase_file_name[next_period_index + 1..period_index] == "d" {
2321          Some(next_period_index)
2322        } else {
2323          Some(period_index)
2324        }
2325      } else {
2326        Some(period_index)
2327      }
2328    } else if NON_DECL_EXTS.contains(&ext) {
2329      Some(period_index)
2330    } else {
2331      None
2332    }
2333  });
2334
2335  let file_name = match period_index {
2336    Some(period_index) => &file_name[..period_index],
2337    None => &file_name,
2338  };
2339  path.with_file_name(format!("{file_name}.{ext}"))
2340}
2341
2342fn to_specifier_display_string(url: &UrlOrPathRef) -> String {
2343  if let Ok(path) = url.path() {
2344    path.display().to_string()
2345  } else {
2346    url.display().to_string()
2347  }
2348}
2349
2350fn throw_invalid_subpath(
2351  subpath: String,
2352  package_json_path: &Path,
2353  internal: bool,
2354  maybe_referrer: Option<&UrlOrPathRef>,
2355) -> InvalidModuleSpecifierError {
2356  let ie = if internal { "imports" } else { "exports" };
2357  let reason = format!(
2358    "request is not a valid subpath for the \"{}\" resolution of {}",
2359    ie,
2360    package_json_path.display(),
2361  );
2362  InvalidModuleSpecifierError {
2363    request: subpath,
2364    reason: Cow::Owned(reason),
2365    maybe_referrer: maybe_referrer.map(to_specifier_display_string),
2366  }
2367}
2368
2369pub fn parse_npm_pkg_name<'a>(
2370  specifier: &'a str,
2371  referrer: &UrlOrPathRef,
2372) -> Result<(&'a str, Cow<'static, str>, bool), InvalidModuleSpecifierError> {
2373  let mut separator_index = specifier.find('/');
2374  let mut valid_package_name = true;
2375  let mut is_scoped = false;
2376  if specifier.is_empty() {
2377    valid_package_name = false;
2378  } else if specifier.starts_with('@') {
2379    is_scoped = true;
2380    if let Some(index) = separator_index {
2381      separator_index = specifier[index + 1..]
2382        .find('/')
2383        .map(|new_index| index + 1 + new_index);
2384    } else {
2385      valid_package_name = false;
2386    }
2387  }
2388
2389  let (package_name, subpath) = if let Some(index) = separator_index {
2390    let (package_name, subpath) = specifier.split_at(index);
2391    (package_name, Cow::Owned(format!(".{}", subpath)))
2392  } else {
2393    (specifier, Cow::Borrowed("."))
2394  };
2395
2396  // Package name cannot have leading . and cannot have percent-encoding or separators.
2397  for ch in package_name.chars() {
2398    if ch == '%' || ch == '\\' {
2399      valid_package_name = false;
2400      break;
2401    }
2402  }
2403
2404  if !valid_package_name {
2405    return Err(errors::InvalidModuleSpecifierError {
2406      request: specifier.to_string(),
2407      reason: Cow::Borrowed("is not a valid package name"),
2408      maybe_referrer: Some(to_specifier_display_string(referrer)),
2409    });
2410  }
2411
2412  Ok((package_name, subpath, is_scoped))
2413}
2414
2415/// Resolves a specifier that is pointing into a node_modules folder.
2416///
2417/// Note: This should be called whenever getting the specifier from
2418/// a Module::External(module) reference because that module might
2419/// not be fully resolved at the time deno_graph is analyzing it
2420/// because the node_modules folder might not exist at that time.
2421pub fn resolve_specifier_into_node_modules(
2422  sys: &impl FsCanonicalize,
2423  specifier: &Url,
2424) -> Url {
2425  deno_path_util::url_to_file_path(specifier)
2426    .ok()
2427    // this path might not exist at the time the graph is being created
2428    // because the node_modules folder might not yet exist
2429    .and_then(|path| {
2430      deno_path_util::fs::canonicalize_path_maybe_not_exists(sys, &path).ok()
2431    })
2432    .and_then(|path| deno_path_util::url_from_file_path(&path).ok())
2433    .unwrap_or_else(|| specifier.clone())
2434}
2435
2436fn pattern_key_compare(a: &str, b: &str) -> i32 {
2437  let a_pattern_index = a.find('*');
2438  let b_pattern_index = b.find('*');
2439
2440  let base_len_a = if let Some(index) = a_pattern_index {
2441    index + 1
2442  } else {
2443    a.len()
2444  };
2445  let base_len_b = if let Some(index) = b_pattern_index {
2446    index + 1
2447  } else {
2448    b.len()
2449  };
2450
2451  if base_len_a > base_len_b {
2452    return -1;
2453  }
2454
2455  if base_len_b > base_len_a {
2456    return 1;
2457  }
2458
2459  if a_pattern_index.is_none() {
2460    return 1;
2461  }
2462
2463  if b_pattern_index.is_none() {
2464    return -1;
2465  }
2466
2467  if a.len() > b.len() {
2468    return -1;
2469  }
2470
2471  if b.len() > a.len() {
2472    return 1;
2473  }
2474
2475  0
2476}
2477
2478/// Gets the corresponding @types package for the provided package name.
2479pub fn types_package_name(package_name: &str) -> String {
2480  debug_assert!(!package_name.starts_with("@types/"));
2481  // Scoped packages will get two underscores for each slash
2482  // https://github.com/DefinitelyTyped/DefinitelyTyped/tree/15f1ece08f7b498f4b9a2147c2a46e94416ca777#what-about-scoped-packages
2483  format!(
2484    "@types/{}",
2485    package_name.trim_start_matches('@').replace('/', "__")
2486  )
2487}
2488
2489/// Node is more lenient joining paths than the url crate is,
2490/// so this function handles that.
2491fn node_join_url(url: &Url, path: &str) -> Result<Url, url::ParseError> {
2492  if let Some(suffix) = path.strip_prefix(".//") {
2493    // specifier had two leading slashes
2494    url.join(&format!("./{}", suffix))
2495  } else {
2496    url.join(path)
2497  }
2498}
2499
2500struct TypesVersions<'a, TSys: FsMetadata> {
2501  dir_path: &'a Path,
2502  value: &'a serde_json::Map<std::string::String, serde_json::Value>,
2503  sys: &'a NodeResolutionSys<TSys>,
2504}
2505
2506impl<'a, TSys: FsMetadata> TypesVersions<'a, TSys> {
2507  pub fn map(&self, search: &str) -> Option<Cow<'a, str>> {
2508    let mut search = search
2509      .strip_prefix("./")
2510      .unwrap_or(search)
2511      .trim_matches('/');
2512    for (key, value) in self.value {
2513      let key = key.strip_suffix("./").unwrap_or(key).trim_matches('/');
2514      let is_match = if key == "*" || key == search {
2515        true
2516      } else if let Some(key_prefix) = key.strip_suffix("/*") {
2517        if let Some(new_search) = search.strip_prefix(key_prefix) {
2518          search = new_search.trim_matches('/');
2519          true
2520        } else {
2521          false
2522        }
2523      } else {
2524        false
2525      };
2526      if !is_match {
2527        continue;
2528      }
2529      if let Some(values) = value.as_array() {
2530        for value in values.iter().filter_map(|s| s.as_str()) {
2531          let value = if let Some(asterisk_index) = value.find('*') {
2532            Cow::Owned(format!(
2533              "{}{}{}",
2534              &value[..asterisk_index],
2535              search,
2536              &value[asterisk_index + 1..]
2537            ))
2538          } else {
2539            Cow::Borrowed(value)
2540          };
2541          let path = self.dir_path.join(value.as_ref());
2542          if self.sys.is_file(&path) {
2543            return Some(value);
2544          }
2545        }
2546      }
2547    }
2548    None
2549  }
2550}
2551
2552#[cfg(test)]
2553mod tests {
2554  use deno_package_json::PackageJsonBins;
2555  use serde_json::json;
2556  use sys_traits::FsCreateDirAll;
2557  use sys_traits::FsWrite;
2558  use sys_traits::impls::InMemorySys;
2559
2560  use super::*;
2561
2562  fn build_package_json(json: Value) -> PackageJson {
2563    PackageJson::load_from_value(PathBuf::from("/package.json"), json).unwrap()
2564  }
2565
2566  #[test]
2567  fn test_resolve_bin_entry_value() {
2568    // should resolve the specified value
2569    {
2570      let pkg_json = build_package_json(json!({
2571        "name": "pkg",
2572        "version": "1.1.1",
2573        "bin": {
2574          "bin1": "./value1",
2575          "bin2": "./value2",
2576          "pkg": "./value3",
2577        }
2578      }));
2579      let bins = match pkg_json.resolve_bins().unwrap() {
2580        PackageJsonBins::Directory(_) => unreachable!(),
2581        PackageJsonBins::Bins(bins) => bins,
2582      };
2583      assert_eq!(
2584        resolve_bin_entry_value(&pkg_json, &bins, Some("bin1")).unwrap(),
2585        pkg_json.dir_path().join("./value1")
2586      );
2587      assert_eq!(
2588        resolve_bin_entry_value(&pkg_json, &bins, Some("pkg")).unwrap(),
2589        pkg_json.dir_path().join("./value3")
2590      );
2591
2592      // should not resolve when specified value does not exist
2593      assert_eq!(
2594        resolve_bin_entry_value(&pkg_json, &bins, Some("other"))
2595          .err()
2596          .unwrap()
2597          .to_string(),
2598        concat!(
2599          "'/package.json' did not have a bin entry for 'other'\n",
2600          "\n",
2601          "Possibilities:\n",
2602          " * npm:pkg@1.1.1/bin1\n",
2603          " * npm:pkg@1.1.1/bin2\n",
2604          " * npm:pkg@1.1.1"
2605        )
2606      );
2607    }
2608
2609    // should not resolve when default value can't be determined
2610    {
2611      let pkg_json = build_package_json(json!({
2612        "name": "pkg",
2613        "version": "1.1.1",
2614        "bin": {
2615          "bin": "./value1",
2616          "bin2": "./value2",
2617        }
2618      }));
2619      let bins = match pkg_json.resolve_bins().unwrap() {
2620        PackageJsonBins::Directory(_) => unreachable!(),
2621        PackageJsonBins::Bins(bins) => bins,
2622      };
2623      assert_eq!(
2624        resolve_bin_entry_value(&pkg_json, &bins, Some("pkg"))
2625          .err()
2626          .unwrap()
2627          .to_string(),
2628        concat!(
2629          "'/package.json' did not have a bin entry for 'pkg'\n",
2630          "\n",
2631          "Possibilities:\n",
2632          " * npm:pkg@1.1.1/bin\n",
2633          " * npm:pkg@1.1.1/bin2",
2634        )
2635      );
2636    }
2637
2638    // should resolve since all the values are the same
2639    {
2640      let pkg_json = build_package_json(json!({
2641        "name": "pkg",
2642        "version": "1.2.3",
2643        "bin": {
2644          "bin1": "./value",
2645          "bin2": "./value",
2646        }
2647      }));
2648      let bins = match pkg_json.resolve_bins().unwrap() {
2649        PackageJsonBins::Directory(_) => unreachable!(),
2650        PackageJsonBins::Bins(bins) => bins,
2651      };
2652      assert_eq!(
2653        resolve_bin_entry_value(&pkg_json, &bins, Some("pkg")).unwrap(),
2654        pkg_json.dir_path().join("./value")
2655      );
2656    }
2657
2658    // should resolve when not specified and only one value
2659    {
2660      let pkg_json = build_package_json(json!({
2661        "name": "pkg",
2662        "version": "1.2.3",
2663        "bin": {
2664          "something": "./value",
2665        }
2666      }));
2667      let bins = match pkg_json.resolve_bins().unwrap() {
2668        PackageJsonBins::Directory(_) => unreachable!(),
2669        PackageJsonBins::Bins(bins) => bins,
2670      };
2671      assert_eq!(
2672        resolve_bin_entry_value(&pkg_json, &bins, None).unwrap(),
2673        pkg_json.dir_path().join("./value")
2674      );
2675    }
2676
2677    // should not resolve when specified and is a string
2678    {
2679      let pkg_json = build_package_json(json!({
2680        "name": "pkg",
2681        "version": "1.2.3",
2682        "bin": "./value",
2683      }));
2684      let bins = match pkg_json.resolve_bins().unwrap() {
2685        PackageJsonBins::Directory(_) => unreachable!(),
2686        PackageJsonBins::Bins(bins) => bins,
2687      };
2688      assert_eq!(
2689        resolve_bin_entry_value(&pkg_json, &bins, Some("path"))
2690          .err()
2691          .unwrap()
2692          .to_string(),
2693        concat!(
2694          "'/package.json' did not have a bin entry for 'path'\n",
2695          "\n",
2696          "Possibilities:\n",
2697          " * npm:pkg@1.2.3"
2698        )
2699      );
2700    }
2701
2702    // no version in the package.json
2703    {
2704      let pkg_json = build_package_json(json!({
2705        "name": "pkg",
2706        "bin": {
2707          "bin1": "./value1",
2708          "bin2": "./value2",
2709        }
2710      }));
2711      let bins = match pkg_json.resolve_bins().unwrap() {
2712        PackageJsonBins::Directory(_) => unreachable!(),
2713        PackageJsonBins::Bins(bins) => bins,
2714      };
2715      assert_eq!(
2716        resolve_bin_entry_value(&pkg_json, &bins, Some("pkg"))
2717          .err()
2718          .unwrap()
2719          .to_string(),
2720        concat!(
2721          "'/package.json' did not have a bin entry for 'pkg'\n",
2722          "\n",
2723          "Possibilities:\n",
2724          " * npm:pkg/bin1\n",
2725          " * npm:pkg/bin2",
2726        )
2727      );
2728    }
2729
2730    // no name or version in the package.json
2731    {
2732      let pkg_json = build_package_json(json!({
2733        "bin": {
2734          "bin1": "./value1",
2735          "bin2": "./value2",
2736        }
2737      }));
2738      let bins = match pkg_json.resolve_bins().unwrap() {
2739        PackageJsonBins::Directory(_) => unreachable!(),
2740        PackageJsonBins::Bins(bins) => bins,
2741      };
2742      assert_eq!(
2743        resolve_bin_entry_value(&pkg_json, &bins, Some("bin"))
2744          .err()
2745          .unwrap()
2746          .to_string(),
2747        concat!(
2748          "'/package.json' did not have a bin entry for 'bin'\n",
2749          "\n",
2750          "Possibilities:\n",
2751          " * bin1\n",
2752          " * bin2",
2753        )
2754      );
2755    }
2756  }
2757
2758  #[test]
2759  fn test_parse_npm_pkg_name() {
2760    let dummy_referrer = Url::parse("http://example.com").unwrap();
2761    let dummy_referrer = UrlOrPathRef::from_url(&dummy_referrer);
2762
2763    assert_eq!(
2764      parse_npm_pkg_name("fetch-blob", &dummy_referrer).unwrap(),
2765      ("fetch-blob", Cow::Borrowed("."), false)
2766    );
2767    assert_eq!(
2768      parse_npm_pkg_name("@vue/plugin-vue", &dummy_referrer).unwrap(),
2769      ("@vue/plugin-vue", Cow::Borrowed("."), true)
2770    );
2771    assert_eq!(
2772      parse_npm_pkg_name("@astrojs/prism/dist/highlighter", &dummy_referrer)
2773        .unwrap(),
2774      (
2775        "@astrojs/prism",
2776        Cow::Owned("./dist/highlighter".to_string()),
2777        true
2778      )
2779    );
2780  }
2781
2782  #[test]
2783  fn test_with_known_extension() {
2784    let cases = &[
2785      ("test", "d.ts", "test.d.ts"),
2786      ("test.d.ts", "ts", "test.ts"),
2787      ("test.worker", "d.ts", "test.worker.d.ts"),
2788      ("test.d.mts", "js", "test.js"),
2789    ];
2790    for (path, ext, expected) in cases {
2791      let actual = with_known_extension(Path::new(path), ext);
2792      assert_eq!(actual.to_string_lossy(), *expected);
2793    }
2794  }
2795
2796  #[test]
2797  fn test_types_package_name() {
2798    assert_eq!(types_package_name("name"), "@types/name");
2799    assert_eq!(
2800      types_package_name("@scoped/package"),
2801      "@types/scoped__package"
2802    );
2803  }
2804
2805  #[test]
2806  fn test_types_versions() {
2807    let dir_path = PathBuf::from("/dir");
2808    let sys = InMemorySys::default();
2809    sys.fs_create_dir_all(dir_path.join("ts3.1")).unwrap();
2810    sys.fs_write(dir_path.join("file.d.ts"), "").unwrap();
2811    sys.fs_write(dir_path.join("ts3.1/file.d.ts"), "").unwrap();
2812    sys.fs_write(dir_path.join("ts3.1/file2.d.ts"), "").unwrap();
2813    let node_resolution_sys = NodeResolutionSys::new(sys, None);
2814
2815    // asterisk key
2816    {
2817      let value = serde_json::json!({
2818        "*": ["ts3.1/*"]
2819      });
2820      let types_versions = TypesVersions {
2821        dir_path: &dir_path,
2822        value: value.as_object().unwrap(),
2823        sys: &node_resolution_sys,
2824      };
2825      assert_eq!(types_versions.map("file.d.ts").unwrap(), "ts3.1/file.d.ts");
2826      assert_eq!(
2827        types_versions.map("file2.d.ts").unwrap(),
2828        "ts3.1/file2.d.ts"
2829      );
2830      assert!(types_versions.map("non_existent/file.d.ts").is_none());
2831    }
2832    // specific file
2833    {
2834      let value = serde_json::json!({
2835        "types.d.ts": ["ts3.1/file.d.ts"]
2836      });
2837      let types_versions = TypesVersions {
2838        dir_path: &dir_path,
2839        value: value.as_object().unwrap(),
2840        sys: &node_resolution_sys,
2841      };
2842      assert_eq!(types_versions.map("types.d.ts").unwrap(), "ts3.1/file.d.ts");
2843      assert!(types_versions.map("file2.d.ts").is_none());
2844    }
2845    // multiple specific files
2846    {
2847      let value = serde_json::json!({
2848        "types.d.ts": ["ts3.1/file.d.ts"],
2849        "other.d.ts": ["ts3.1/file2.d.ts"],
2850      });
2851      let types_versions = TypesVersions {
2852        dir_path: &dir_path,
2853        value: value.as_object().unwrap(),
2854        sys: &node_resolution_sys,
2855      };
2856      assert_eq!(types_versions.map("types.d.ts").unwrap(), "ts3.1/file.d.ts");
2857      assert_eq!(
2858        types_versions.map("other.d.ts").unwrap(),
2859        "ts3.1/file2.d.ts"
2860      );
2861      assert!(types_versions.map("file2.d.ts").is_none());
2862    }
2863    // existing fallback
2864    {
2865      let value = serde_json::json!({
2866        "*": ["ts3.1/*", "ts3.1/file2.d.ts"]
2867      });
2868      let types_versions = TypesVersions {
2869        dir_path: &dir_path,
2870        value: value.as_object().unwrap(),
2871        sys: &node_resolution_sys,
2872      };
2873      assert_eq!(
2874        types_versions.map("testing/types.d.ts").unwrap(),
2875        "ts3.1/file2.d.ts"
2876      );
2877    }
2878    // text then asterisk in key
2879    {
2880      let value = serde_json::json!({
2881        "sub/*": ["ts3.1/file.d.ts"]
2882      });
2883      let types_versions = TypesVersions {
2884        dir_path: &dir_path,
2885        value: value.as_object().unwrap(),
2886        sys: &node_resolution_sys,
2887      };
2888      assert_eq!(
2889        types_versions.map("sub/types.d.ts").unwrap(),
2890        "ts3.1/file.d.ts"
2891      );
2892    }
2893    // text then asterisk in key and asterisk in value
2894    {
2895      let value = serde_json::json!({
2896        "sub/*": ["ts3.1/*"]
2897      });
2898      let types_versions = TypesVersions {
2899        dir_path: &dir_path,
2900        value: value.as_object().unwrap(),
2901        sys: &node_resolution_sys,
2902      };
2903      assert_eq!(
2904        types_versions.map("sub/file.d.ts").unwrap(),
2905        "ts3.1/file.d.ts"
2906      );
2907    }
2908  }
2909
2910  #[test]
2911  fn test_resolve_execution_path_from_npx_shim() {
2912    // example shim on unix
2913    let unix_shim = r#"#!/usr/bin/env node
2914"use strict";
2915console.log('Hi!');
2916"#;
2917    let path = PathBuf::from("/node_modules/.bin/example");
2918    assert_eq!(
2919      resolve_execution_path_from_npx_shim(Cow::Borrowed(&path), unix_shim)
2920        .unwrap(),
2921      path
2922    );
2923    // example shim on unix
2924    let unix_shim = r#"#!/usr/bin/env -S node
2925"use strict";
2926console.log('Hi!');
2927"#;
2928    let path = PathBuf::from("/node_modules/.bin/example");
2929    assert_eq!(
2930      resolve_execution_path_from_npx_shim(Cow::Borrowed(&path), unix_shim)
2931        .unwrap(),
2932      path
2933    );
2934    // example shim on windows
2935    let windows_shim = r#"#!/bin/sh
2936basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
2937
2938case `uname` in
2939    *CYGWIN*|*MINGW*|*MSYS*) basedir=`cygpath -w "$basedir"`;;
2940esac
2941
2942if [ -x "$basedir/node" ]; then
2943  exec "$basedir/node"  "$basedir/../example/bin/example" "$@"
2944else
2945  exec node  "$basedir/../example/bin/example" "$@"
2946fi"#;
2947    assert_eq!(
2948      resolve_execution_path_from_npx_shim(Cow::Borrowed(&path), windows_shim)
2949        .unwrap(),
2950      path.parent().unwrap().join("../example/bin/example")
2951    );
2952  }
2953}