Skip to main content

node_resolver/
resolution.rs

1// Copyright 2018-2026 the Deno authors. MIT license.
2
3use std::borrow::Cow;
4use std::collections::BTreeMap;
5use std::fmt::Debug;
6use std::io::BufReader;
7use std::io::Read;
8use std::path::Path;
9use std::path::PathBuf;
10
11use anyhow::Error as AnyError;
12use anyhow::bail;
13use deno_media_type::MediaType;
14use deno_package_json::PackageJson;
15use deno_package_json::PackageJsonRc;
16use deno_path_util::url_to_file_path;
17use deno_semver::Version;
18use deno_semver::VersionReq;
19use lazy_regex::Lazy;
20use regex::Regex;
21use serde_json::Map;
22use serde_json::Value;
23use sys_traits::FileType;
24use sys_traits::FsCanonicalize;
25use sys_traits::FsDirEntry;
26use sys_traits::FsMetadata;
27use sys_traits::FsOpen;
28use sys_traits::FsRead;
29use sys_traits::FsReadDir;
30use sys_traits::OpenOptions;
31use url::Url;
32
33use crate::InNpmPackageChecker;
34use crate::IsBuiltInNodeModuleChecker;
35use crate::NpmPackageFolderResolver;
36use crate::PackageJsonResolverRc;
37use crate::PathClean;
38use crate::cache::NodeResolutionSys;
39use crate::errors;
40use crate::errors::DataUrlReferrerError;
41use crate::errors::FinalizeResolutionError;
42use crate::errors::InvalidModuleSpecifierError;
43use crate::errors::InvalidPackageTargetError;
44use crate::errors::LegacyResolveError;
45use crate::errors::MissingPkgJsonError;
46use crate::errors::ModuleNotFoundError;
47use crate::errors::NodeJsErrorCode;
48use crate::errors::NodeJsErrorCoded;
49use crate::errors::NodeResolveError;
50use crate::errors::NodeResolveRelativeJoinError;
51use crate::errors::PackageExportsResolveError;
52use crate::errors::PackageImportNotDefinedError;
53use crate::errors::PackageImportsResolveError;
54use crate::errors::PackageImportsResolveErrorKind;
55use crate::errors::PackagePathNotExportedError;
56use crate::errors::PackageResolveError;
57use crate::errors::PackageSubpathFromDenoModuleResolveError;
58use crate::errors::PackageSubpathResolveError;
59use crate::errors::PackageSubpathResolveErrorKind;
60use crate::errors::PackageTargetNotFoundError;
61use crate::errors::PackageTargetResolveError;
62use crate::errors::PackageTargetResolveErrorKind;
63use crate::errors::ResolvePkgJsonBinExportError;
64use crate::errors::ResolvePkgNpmBinaryCommandsError;
65use crate::errors::TypesNotFoundError;
66use crate::errors::TypesNotFoundErrorData;
67use crate::errors::UnknownBuiltInNodeModuleError;
68use crate::errors::UnsupportedDirImportError;
69use crate::errors::UnsupportedEsmUrlSchemeError;
70use crate::path::UrlOrPath;
71use crate::path::UrlOrPathRef;
72
73pub static IMPORT_CONDITIONS: &[Cow<'static, str>] = &[
74  Cow::Borrowed("deno"),
75  Cow::Borrowed("node"),
76  Cow::Borrowed("import"),
77];
78pub static REQUIRE_CONDITIONS: &[Cow<'static, str>] =
79  &[Cow::Borrowed("require"), Cow::Borrowed("node")];
80static TYPES_ONLY_CONDITIONS: &[Cow<'static, str>] = &[Cow::Borrowed("types")];
81
82#[derive(Debug, Default, Clone)]
83pub struct NodeConditionOptions {
84  pub conditions: Vec<Cow<'static, str>>,
85  /// Provide a value to override the default import conditions.
86  ///
87  /// Defaults to `["deno", "node", "import"]`
88  pub import_conditions_override: Option<Vec<Cow<'static, str>>>,
89  /// Provide a value to override the default require conditions.
90  ///
91  /// Defaults to `["require", "node"]`
92  pub require_conditions_override: Option<Vec<Cow<'static, str>>>,
93}
94
95#[derive(Debug, Clone)]
96struct ConditionResolver {
97  import_conditions: Cow<'static, [Cow<'static, str>]>,
98  require_conditions: Cow<'static, [Cow<'static, str>]>,
99}
100
101impl ConditionResolver {
102  pub fn new(options: NodeConditionOptions) -> Self {
103    fn combine_conditions(
104      user_conditions: Cow<'_, [Cow<'static, str>]>,
105      override_default: Option<Vec<Cow<'static, str>>>,
106      default_conditions: &'static [Cow<'static, str>],
107    ) -> Cow<'static, [Cow<'static, str>]> {
108      let default_conditions = override_default
109        .map(Cow::Owned)
110        .unwrap_or(Cow::Borrowed(default_conditions));
111      if user_conditions.is_empty() {
112        default_conditions
113      } else {
114        let mut new =
115          Vec::with_capacity(user_conditions.len() + default_conditions.len());
116        let mut append =
117          |conditions: Cow<'_, [Cow<'static, str>]>| match conditions {
118            Cow::Borrowed(conditions) => new.extend(conditions.iter().cloned()),
119            Cow::Owned(conditions) => new.extend(conditions),
120          };
121        append(user_conditions);
122        append(default_conditions);
123        Cow::Owned(new)
124      }
125    }
126
127    Self {
128      import_conditions: combine_conditions(
129        Cow::Borrowed(&options.conditions),
130        options.import_conditions_override,
131        IMPORT_CONDITIONS,
132      ),
133      require_conditions: combine_conditions(
134        Cow::Owned(options.conditions),
135        options.require_conditions_override,
136        REQUIRE_CONDITIONS,
137      ),
138    }
139  }
140
141  pub fn resolve(
142    &self,
143    resolution_mode: ResolutionMode,
144  ) -> &[Cow<'static, str>] {
145    match resolution_mode {
146      ResolutionMode::Import => &self.import_conditions,
147      ResolutionMode::Require => &self.require_conditions,
148    }
149  }
150
151  pub fn require_conditions(&self) -> &[Cow<'static, str>] {
152    &self.require_conditions
153  }
154}
155
156#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
157pub enum ResolutionMode {
158  Import,
159  Require,
160}
161
162impl ResolutionMode {
163  pub fn default_conditions(&self) -> &'static [Cow<'static, str>] {
164    match self {
165      ResolutionMode::Import => IMPORT_CONDITIONS,
166      ResolutionMode::Require => REQUIRE_CONDITIONS,
167    }
168  }
169
170  #[cfg(feature = "graph")]
171  pub fn from_deno_graph(mode: deno_graph::source::ResolutionMode) -> Self {
172    use deno_graph::source::ResolutionMode::*;
173    match mode {
174      Import => Self::Import,
175      Require => Self::Require,
176    }
177  }
178}
179
180#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
181pub enum NodeResolutionKind {
182  Execution,
183  Types,
184}
185
186impl NodeResolutionKind {
187  pub fn is_types(&self) -> bool {
188    matches!(self, NodeResolutionKind::Types)
189  }
190
191  #[cfg(feature = "graph")]
192  pub fn from_deno_graph(kind: deno_graph::source::ResolutionKind) -> Self {
193    use deno_graph::source::ResolutionKind::*;
194    match kind {
195      Execution => Self::Execution,
196      Types => Self::Types,
197    }
198  }
199}
200
201#[derive(Debug)]
202pub enum NodeResolution {
203  Module(UrlOrPath),
204  BuiltIn(String),
205}
206
207impl NodeResolution {
208  pub fn into_url(self) -> Result<Url, NodeResolveError> {
209    match self {
210      Self::Module(u) => Ok(u.into_url()?),
211      Self::BuiltIn(specifier) => Ok(if specifier.starts_with("node:") {
212        Url::parse(&specifier).unwrap()
213      } else {
214        Url::parse(&format!("node:{specifier}")).unwrap()
215      }),
216    }
217  }
218}
219
220struct LocalPath {
221  path: PathBuf,
222  known_exists: bool,
223}
224
225enum LocalUrlOrPath {
226  Url(Url),
227  Path(LocalPath),
228}
229
230impl LocalUrlOrPath {
231  pub fn into_url_or_path(self) -> UrlOrPath {
232    match self {
233      LocalUrlOrPath::Url(url) => UrlOrPath::Url(url),
234      LocalUrlOrPath::Path(local_path) => UrlOrPath::Path(local_path.path),
235    }
236  }
237}
238
239/// This struct helps ensure we remember to probe for
240/// declaration files and to prevent accidentally probing
241/// multiple times.
242struct MaybeTypesResolvedUrl(LocalUrlOrPath);
243
244/// Kind of method that resolution succeeded with.
245enum ResolvedMethod {
246  Url,
247  RelativeOrAbsolute,
248  PackageImports,
249  PackageExports,
250  PackageSubPath,
251}
252
253#[derive(Debug, Default, Clone)]
254pub struct NodeResolverOptions {
255  pub conditions: NodeConditionOptions,
256  pub is_browser_platform: bool,
257  pub bundle_mode: bool,
258  /// TypeScript version to use for typesVersions resolution and
259  /// `types@req` exports resolution.
260  pub typescript_version: Option<Version>,
261}
262
263#[derive(Debug)]
264struct ResolutionConfig {
265  pub bundle_mode: bool,
266  pub prefer_browser_field: bool,
267  pub typescript_version: Option<Version>,
268}
269
270#[sys_traits::auto_impl]
271pub trait NodeResolverSys:
272  FsCanonicalize + FsMetadata + FsRead + FsReadDir + FsOpen
273{
274}
275
276#[allow(clippy::disallowed_types)]
277pub type NodeResolverRc<
278  TInNpmPackageChecker,
279  TIsBuiltInNodeModuleChecker,
280  TNpmPackageFolderResolver,
281  TSys,
282> = deno_maybe_sync::MaybeArc<
283  NodeResolver<
284    TInNpmPackageChecker,
285    TIsBuiltInNodeModuleChecker,
286    TNpmPackageFolderResolver,
287    TSys,
288  >,
289>;
290
291#[derive(Debug)]
292pub struct NodeResolver<
293  TInNpmPackageChecker: InNpmPackageChecker,
294  TIsBuiltInNodeModuleChecker: IsBuiltInNodeModuleChecker,
295  TNpmPackageFolderResolver: NpmPackageFolderResolver,
296  TSys: NodeResolverSys,
297> {
298  in_npm_pkg_checker: TInNpmPackageChecker,
299  is_built_in_node_module_checker: TIsBuiltInNodeModuleChecker,
300  npm_pkg_folder_resolver: TNpmPackageFolderResolver,
301  pkg_json_resolver: PackageJsonResolverRc<TSys>,
302  sys: NodeResolutionSys<TSys>,
303  condition_resolver: ConditionResolver,
304  resolution_config: ResolutionConfig,
305}
306
307impl<
308  TInNpmPackageChecker: InNpmPackageChecker,
309  TIsBuiltInNodeModuleChecker: IsBuiltInNodeModuleChecker,
310  TNpmPackageFolderResolver: NpmPackageFolderResolver,
311  TSys: NodeResolverSys,
312>
313  NodeResolver<
314    TInNpmPackageChecker,
315    TIsBuiltInNodeModuleChecker,
316    TNpmPackageFolderResolver,
317    TSys,
318  >
319{
320  pub fn new(
321    in_npm_pkg_checker: TInNpmPackageChecker,
322    is_built_in_node_module_checker: TIsBuiltInNodeModuleChecker,
323    npm_pkg_folder_resolver: TNpmPackageFolderResolver,
324    pkg_json_resolver: PackageJsonResolverRc<TSys>,
325    sys: NodeResolutionSys<TSys>,
326    options: NodeResolverOptions,
327  ) -> Self {
328    Self {
329      in_npm_pkg_checker,
330      is_built_in_node_module_checker,
331      npm_pkg_folder_resolver,
332      pkg_json_resolver,
333      sys,
334      condition_resolver: ConditionResolver::new(NodeConditionOptions {
335        conditions: options.conditions.conditions,
336        import_conditions_override: options
337          .conditions
338          .import_conditions_override
339          .or_else(|| {
340            if options.is_browser_platform {
341              Some(vec![Cow::Borrowed("browser"), Cow::Borrowed("import")])
342            } else {
343              None
344            }
345          }),
346        require_conditions_override: options
347          .conditions
348          .require_conditions_override
349          .or_else(|| {
350            if options.is_browser_platform {
351              Some(vec![Cow::Borrowed("browser"), Cow::Borrowed("require")])
352            } else {
353              None
354            }
355          }),
356      }),
357      resolution_config: ResolutionConfig {
358        bundle_mode: options.bundle_mode,
359        prefer_browser_field: options.is_browser_platform,
360        typescript_version: options.typescript_version,
361      },
362    }
363  }
364
365  pub fn require_conditions(&self) -> &[Cow<'static, str>] {
366    self.condition_resolver.require_conditions()
367  }
368
369  pub fn in_npm_package(&self, specifier: &Url) -> bool {
370    self.in_npm_pkg_checker.in_npm_package(specifier)
371  }
372
373  #[inline(always)]
374  pub fn is_builtin_node_module(&self, specifier: &str) -> bool {
375    self
376      .is_built_in_node_module_checker
377      .is_builtin_node_module(specifier)
378  }
379
380  /// This function is an implementation of `defaultResolve` in
381  /// `lib/internal/modules/esm/resolve.js` from Node.
382  pub fn resolve(
383    &self,
384    specifier: &str,
385    referrer: &Url,
386    resolution_mode: ResolutionMode,
387    resolution_kind: NodeResolutionKind,
388  ) -> Result<NodeResolution, NodeResolveError> {
389    // Note: if we are here, then the referrer is an esm module
390    // TODO(bartlomieju): skipped "policy" part as we don't plan to support it
391
392    if self.is_builtin_node_module(specifier) {
393      return Ok(NodeResolution::BuiltIn(specifier.to_string()));
394    }
395
396    if let Ok(url) = Url::parse(specifier) {
397      if url.scheme() == "data" {
398        return Ok(NodeResolution::Module(UrlOrPath::Url(url)));
399      }
400
401      if let Some(module_name) =
402        self.get_module_name_from_builtin_node_module_url(&url)?
403      {
404        return Ok(NodeResolution::BuiltIn(module_name.to_string()));
405      }
406
407      let protocol = url.scheme();
408
409      if protocol != "file" && protocol != "data" {
410        return Err(
411          UnsupportedEsmUrlSchemeError {
412            url_scheme: protocol.to_string(),
413          }
414          .into(),
415        );
416      }
417
418      // todo(dsherret): this seems wrong
419      if referrer.scheme() == "data" {
420        let url = referrer
421          .join(specifier)
422          .map_err(|source| DataUrlReferrerError { source })?;
423        return Ok(NodeResolution::Module(UrlOrPath::Url(url)));
424      }
425    }
426
427    let conditions = self.condition_resolver.resolve(resolution_mode);
428    let referrer = UrlOrPathRef::from_url(referrer);
429    let (url, resolved_kind) = self.module_resolve(
430      specifier,
431      &referrer,
432      resolution_mode,
433      conditions,
434      resolution_kind,
435    )?;
436
437    let url_or_path = self.finalize_resolution(
438      url,
439      resolved_kind,
440      resolution_mode,
441      conditions,
442      resolution_kind,
443      Some(&referrer),
444    )?;
445    let resolve_response = NodeResolution::Module(url_or_path);
446    // TODO(bartlomieju): skipped checking errors for commonJS resolution and
447    // "preserveSymlinksMain"/"preserveSymlinks" options.
448    Ok(resolve_response)
449  }
450
451  fn module_resolve(
452    &self,
453    specifier: &str,
454    referrer: &UrlOrPathRef,
455    resolution_mode: ResolutionMode,
456    conditions: &[Cow<'static, str>],
457    resolution_kind: NodeResolutionKind,
458  ) -> Result<(MaybeTypesResolvedUrl, ResolvedMethod), NodeResolveError> {
459    if should_be_treated_as_relative_or_absolute_path(specifier) {
460      let referrer_url = referrer.url()?;
461      let url = node_join_url(referrer_url, specifier).map_err(|err| {
462        NodeResolveRelativeJoinError {
463          path: specifier.to_string(),
464          base: referrer_url.clone(),
465          source: err,
466        }
467      })?;
468      let url = self.maybe_resolve_types(
469        LocalUrlOrPath::Url(url),
470        Some(referrer),
471        resolution_mode,
472        conditions,
473        resolution_kind,
474      )?;
475      Ok((url, ResolvedMethod::RelativeOrAbsolute))
476    } else if specifier.starts_with('#') {
477      let pkg_config = self
478        .pkg_json_resolver
479        .get_closest_package_json(referrer.path()?)
480        .map_err(PackageImportsResolveErrorKind::PkgJsonLoad)
481        .map_err(|err| PackageImportsResolveError(Box::new(err)))?;
482      Ok((
483        self.package_imports_resolve_internal(
484          specifier,
485          Some(referrer),
486          resolution_mode,
487          pkg_config.as_deref(),
488          conditions,
489          resolution_kind,
490        )?,
491        ResolvedMethod::PackageImports,
492      ))
493    } else if let Ok(url) = Url::parse(specifier) {
494      let url_or_path = self.maybe_resolve_types(
495        LocalUrlOrPath::Url(url),
496        Some(referrer),
497        resolution_mode,
498        conditions,
499        resolution_kind,
500      )?;
501      Ok((url_or_path, ResolvedMethod::Url))
502    } else {
503      Ok(self.package_resolve(
504        specifier,
505        referrer,
506        resolution_mode,
507        conditions,
508        resolution_kind,
509      )?)
510    }
511  }
512
513  fn finalize_resolution(
514    &self,
515    resolved: MaybeTypesResolvedUrl,
516    resolved_method: ResolvedMethod,
517    resolution_mode: ResolutionMode,
518    conditions: &[Cow<'static, str>],
519    resolution_kind: NodeResolutionKind,
520    maybe_referrer: Option<&UrlOrPathRef>,
521  ) -> Result<UrlOrPath, FinalizeResolutionError> {
522    let encoded_sep_re = lazy_regex::regex!(r"%2F|%2C");
523
524    let resolved = resolved.0;
525    let text = match &resolved {
526      LocalUrlOrPath::Url(url) => Cow::Borrowed(url.as_str()),
527      LocalUrlOrPath::Path(LocalPath { path, .. }) => path.to_string_lossy(),
528    };
529    if encoded_sep_re.is_match(&text) {
530      return Err(
531        errors::InvalidModuleSpecifierError {
532          request: text.into_owned(),
533          reason: Cow::Borrowed(
534            "must not include encoded \"/\" or \"\\\\\" characters",
535          ),
536          maybe_referrer: maybe_referrer.map(|r| match r.path() {
537            // in this case, prefer showing the path string
538            Ok(path) => path.display().to_string(),
539            Err(_) => r.display().to_string(),
540          }),
541        }
542        .into(),
543      );
544    }
545
546    let (path, maybe_url) = match resolved {
547      LocalUrlOrPath::Url(url) => {
548        if url.scheme() == "file" {
549          (url_to_file_path(&url)?, Some(url))
550        } else {
551          return Ok(UrlOrPath::Url(url));
552        }
553      }
554      LocalUrlOrPath::Path(LocalPath { path, known_exists }) => {
555        if known_exists {
556          // no need to do the finalization checks
557          return Ok(UrlOrPath::Path(path));
558        } else {
559          (path, None)
560        }
561      }
562    };
563
564    // TODO(bartlomieju): currently not supported
565    // if (getOptionValue('--experimental-specifier-resolution') === 'node') {
566    //   ...
567    // }
568
569    let p_str = path.to_str().unwrap();
570    let path = match p_str.strip_suffix('/') {
571      Some(s) => Cow::Borrowed(Path::new(s)),
572      None => Cow::Owned(path),
573    };
574
575    let maybe_file_type = self.sys.get_file_type(&path);
576    match maybe_file_type {
577      Ok(FileType::Dir) => {
578        if resolution_mode == ResolutionMode::Import
579          && !self.resolution_config.bundle_mode
580        {
581          let suggestion = self.directory_import_suggestion(&path);
582          Err(
583            UnsupportedDirImportError {
584              dir_url: UrlOrPath::Path(path.into_owned()),
585              maybe_referrer: maybe_referrer.map(|r| r.display()),
586              suggestion,
587            }
588            .into(),
589          )
590        } else {
591          // prefer the file over the directory
592          let path_with_ext = with_known_extension(&path, "js");
593          if self.sys.is_file(&path_with_ext) {
594            Ok(UrlOrPath::Path(path_with_ext))
595          } else {
596            let (resolved_url, resolved_method) = self
597              .resolve_package_dir_subpath(
598                &path,
599                ".",
600                maybe_referrer,
601                resolution_mode,
602                conditions,
603                resolution_kind,
604              )?;
605            self.finalize_resolution(
606              resolved_url,
607              resolved_method,
608              resolution_mode,
609              conditions,
610              resolution_kind,
611              maybe_referrer,
612            )
613          }
614        }
615      }
616      Ok(FileType::File) => {
617        // prefer returning the url to avoid re-allocating in the CLI crate
618        Ok(
619          maybe_url
620            .map(UrlOrPath::Url)
621            .unwrap_or(UrlOrPath::Path(path.into_owned())),
622        )
623      }
624      _ => {
625        if let Err(e) = maybe_file_type
626          && (resolution_mode == ResolutionMode::Require
627            || self.resolution_config.bundle_mode)
628          && e.kind() == std::io::ErrorKind::NotFound
629        {
630          let file_with_ext = with_known_extension(&path, "js");
631          if self.sys.is_file(&file_with_ext) {
632            return Ok(UrlOrPath::Path(file_with_ext));
633          }
634        }
635
636        Err(
637          ModuleNotFoundError {
638            suggested_ext: self
639              .module_not_found_ext_suggestion(&path, resolved_method),
640            specifier: UrlOrPath::Path(path.into_owned()),
641            maybe_referrer: maybe_referrer.map(|r| r.display()),
642          }
643          .into(),
644        )
645      }
646    }
647  }
648
649  fn module_not_found_ext_suggestion(
650    &self,
651    path: &Path,
652    resolved_method: ResolvedMethod,
653  ) -> Option<&'static str> {
654    fn should_probe(path: &Path, resolved_method: ResolvedMethod) -> bool {
655      if MediaType::from_path(path) != MediaType::Unknown {
656        return false;
657      }
658      match resolved_method {
659        ResolvedMethod::Url
660        | ResolvedMethod::RelativeOrAbsolute
661        | ResolvedMethod::PackageSubPath => true,
662        ResolvedMethod::PackageImports | ResolvedMethod::PackageExports => {
663          false
664        }
665      }
666    }
667
668    if should_probe(path, resolved_method) {
669      ["js", "mjs", "cjs"]
670        .into_iter()
671        .find(|ext| self.sys.is_file(&with_known_extension(path, ext)))
672    } else {
673      None
674    }
675  }
676
677  fn directory_import_suggestion(
678    &self,
679    dir_import_path: &Path,
680  ) -> Option<String> {
681    let dir_index_paths = ["index.mjs", "index.js", "index.cjs"]
682      .into_iter()
683      .map(|file_name| dir_import_path.join(file_name));
684    let file_paths = [
685      with_known_extension(dir_import_path, "js"),
686      with_known_extension(dir_import_path, "mjs"),
687      with_known_extension(dir_import_path, "cjs"),
688    ];
689    dir_index_paths
690      .chain(file_paths)
691      .chain(
692        std::iter::once_with(|| {
693          // check if this directory has a package.json
694          let package_json_path = dir_import_path.join("package.json");
695          let pkg_json = self
696            .pkg_json_resolver
697            .load_package_json(&package_json_path)
698            .ok()??;
699          let main = pkg_json.main.as_ref()?;
700          Some(dir_import_path.join(main))
701        })
702        .flatten(),
703      )
704      .map(|p| deno_path_util::normalize_path(Cow::Owned(p)))
705      .find(|p| self.sys.is_file(p))
706      .and_then(|suggested_file_path| {
707        let pkg_json = self
708          .pkg_json_resolver
709          .get_closest_package_jsons(&suggested_file_path)
710          .filter_map(|pkg_json| pkg_json.ok())
711          .find(|p| p.name.is_some())?;
712        let pkg_name = pkg_json.name.as_ref()?;
713        let sub_path = suggested_file_path
714          .strip_prefix(pkg_json.dir_path())
715          .ok()?
716          .to_string_lossy()
717          .replace("\\", "/");
718        Some(format!("{}/{}", pkg_name, sub_path))
719      })
720  }
721
722  pub fn resolve_package_subpath_from_deno_module(
723    &self,
724    package_dir: &Path,
725    package_subpath: Option<&str>,
726    maybe_referrer: Option<&Url>,
727    resolution_mode: ResolutionMode,
728    resolution_kind: NodeResolutionKind,
729  ) -> Result<UrlOrPath, PackageSubpathFromDenoModuleResolveError> {
730    // todo(dsherret): don't allocate a string here (maybe use an
731    // enum that says the subpath is not prefixed with a ./)
732    let package_subpath = package_subpath
733      .map(|s| Cow::Owned(format!("./{s}")))
734      .unwrap_or_else(|| Cow::Borrowed("."));
735    let maybe_referrer = maybe_referrer.map(UrlOrPathRef::from_url);
736    let conditions = self.condition_resolver.resolve(resolution_mode);
737    let (resolved_url, resolved_method) = self.resolve_package_dir_subpath(
738      package_dir,
739      &package_subpath,
740      maybe_referrer.as_ref(),
741      resolution_mode,
742      conditions,
743      resolution_kind,
744    )?;
745    let url_or_path = self.finalize_resolution(
746      resolved_url,
747      resolved_method,
748      resolution_mode,
749      conditions,
750      resolution_kind,
751      maybe_referrer.as_ref(),
752    )?;
753    Ok(url_or_path)
754  }
755
756  pub fn resolve_binary_export(
757    &self,
758    package_folder: &Path,
759    sub_path: Option<&str>,
760  ) -> Result<BinValue, ResolvePkgJsonBinExportError> {
761    let (pkg_json, items) = self
762      .resolve_npm_binary_commands_for_package_with_pkg_json(package_folder)?;
763    let path =
764      resolve_bin_entry_value(&pkg_json, &items, sub_path).map_err(|err| {
765        ResolvePkgJsonBinExportError::InvalidBinProperty {
766          message: err.to_string(),
767        }
768      })?;
769    Ok(path.clone())
770  }
771
772  pub fn resolve_npm_binary_commands_for_package(
773    &self,
774    package_folder: &Path,
775  ) -> Result<BTreeMap<String, BinValue>, ResolvePkgNpmBinaryCommandsError> {
776    let (_pkg_json, items) = self
777      .resolve_npm_binary_commands_for_package_with_pkg_json(package_folder)?;
778    Ok(items)
779  }
780
781  fn resolve_npm_binary_commands_for_package_with_pkg_json(
782    &self,
783    package_folder: &Path,
784  ) -> Result<
785    (PackageJsonRc, BTreeMap<String, BinValue>),
786    ResolvePkgNpmBinaryCommandsError,
787  > {
788    let pkg_json_path = package_folder.join("package.json");
789    let Some(package_json) =
790      self.pkg_json_resolver.load_package_json(&pkg_json_path)?
791    else {
792      return Err(ResolvePkgNpmBinaryCommandsError::MissingPkgJson(
793        MissingPkgJsonError { pkg_json_path },
794      ));
795    };
796    let bins = package_json.resolve_bins()?;
797    // TODO(bartlomieju): skipped checking errors for commonJS resolution and
798    // "preserveSymlinksMain"/"preserveSymlinks" options.
799    let items = match bins {
800      deno_package_json::PackageJsonBins::Directory(path_buf) => {
801        self.resolve_npm_commands_from_bin_dir(&path_buf)
802      }
803      deno_package_json::PackageJsonBins::Bins(items) => items
804        .into_iter()
805        .filter_map(|(command, path)| {
806          let bin_value = bin_value_from_file(&path, &self.sys)?;
807          Some((command, bin_value))
808        })
809        .collect(),
810    };
811    Ok((package_json, items))
812  }
813
814  pub fn resolve_npm_commands_from_bin_dir(
815    &self,
816    bin_dir: &Path,
817  ) -> BTreeMap<String, BinValue> {
818    log::debug!("Resolving npm commands in '{}'.", bin_dir.display());
819    let mut result = BTreeMap::new();
820    match self.sys.fs_read_dir(bin_dir) {
821      Ok(entries) => {
822        for entry in entries {
823          let Ok(entry) = entry else {
824            continue;
825          };
826          if let Some((command, bin_value)) =
827            self.resolve_bin_dir_entry_command(entry)
828          {
829            result.insert(command, bin_value);
830          }
831        }
832      }
833      Err(err) => {
834        log::debug!("Failed read_dir for '{}': {:#}", bin_dir.display(), err);
835      }
836    }
837    result
838  }
839
840  fn resolve_bin_dir_entry_command(
841    &self,
842    entry: TSys::ReadDirEntry,
843  ) -> Option<(String, BinValue)> {
844    if entry.path().extension().is_some() {
845      return None; // only look at files without extensions (even on Windows)
846    }
847    let file_type = entry.file_type().ok()?;
848    let path = if file_type.is_file() {
849      entry.path()
850    } else if file_type.is_symlink() {
851      Cow::Owned(self.sys.fs_canonicalize(entry.path()).ok()?)
852    } else {
853      return None;
854    };
855    let command_name = entry.file_name().to_string_lossy().into_owned();
856    let bin_value = bin_value_from_file(&path, &self.sys)?;
857    Some((command_name, bin_value))
858  }
859
860  /// Resolves an npm package folder path from the specified referrer.
861  pub fn resolve_package_folder_from_package(
862    &self,
863    package_name: &str,
864    referrer: &UrlOrPathRef,
865  ) -> Result<PathBuf, errors::PackageFolderResolveError> {
866    self
867      .npm_pkg_folder_resolver
868      .resolve_package_folder_from_package(package_name, 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.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 resolve = |package_dir: &Path| {
1641      self.resolve_package_dir_subpath(
1642        package_dir,
1643        package_subpath,
1644        Some(referrer),
1645        resolution_mode,
1646        conditions,
1647        resolution_kind,
1648      )
1649    };
1650    let result: Result<_, PackageResolveError> = self
1651      .npm_pkg_folder_resolver
1652      .resolve_package_folder_from_package(package_name, referrer)
1653      .map_err(|err| err.into())
1654      .and_then(|package_dir| resolve(&package_dir).map_err(|e| e.into()));
1655    if resolution_kind.is_types() && result.is_err() {
1656      // try to resolve with the @types package based on the package name
1657      let maybe_types_package_dir = self
1658        .resolve_types_package_folder_with_name_and_version(
1659          package_name,
1660          None,
1661          Some(referrer),
1662        );
1663      if let Some(types_package_dir) = maybe_types_package_dir
1664        && let Ok(result) = resolve(&types_package_dir)
1665      {
1666        return Ok(result);
1667      }
1668    }
1669    result
1670  }
1671
1672  #[allow(clippy::too_many_arguments)]
1673  fn resolve_package_dir_subpath(
1674    &self,
1675    package_dir_path: &Path,
1676    package_subpath: &str,
1677    maybe_referrer: Option<&UrlOrPathRef>,
1678    resolution_mode: ResolutionMode,
1679    conditions: &[Cow<'static, str>],
1680    resolution_kind: NodeResolutionKind,
1681  ) -> Result<(MaybeTypesResolvedUrl, ResolvedMethod), PackageSubpathResolveError>
1682  {
1683    let package_json_path = package_dir_path.join("package.json");
1684    match self
1685      .pkg_json_resolver
1686      .load_package_json(&package_json_path)?
1687    {
1688      Some(pkg_json) => {
1689        let result = self.resolve_package_subpath(
1690          &pkg_json,
1691          package_subpath,
1692          maybe_referrer,
1693          resolution_mode,
1694          conditions,
1695          resolution_kind,
1696        );
1697        if resolution_kind.is_types()
1698          && result.is_err()
1699          && let Some(types_pkg_dir) = self
1700            .resolve_types_package_folder_from_package_json(
1701              &pkg_json,
1702              maybe_referrer,
1703            )
1704          && let Ok(result) = self.resolve_package_dir_subpath(
1705            &types_pkg_dir,
1706            package_subpath,
1707            maybe_referrer,
1708            resolution_mode,
1709            conditions,
1710            resolution_kind,
1711          )
1712        {
1713          Ok(result)
1714        } else {
1715          result
1716        }
1717      }
1718      None => self
1719        .resolve_package_subpath_no_pkg_json(
1720          package_dir_path,
1721          package_subpath,
1722          maybe_referrer,
1723          resolution_mode,
1724          conditions,
1725          resolution_kind,
1726        )
1727        .map(|url| (url, ResolvedMethod::PackageSubPath))
1728        .map_err(|err| {
1729          PackageSubpathResolveErrorKind::LegacyResolve(err).into()
1730        }),
1731    }
1732  }
1733
1734  #[allow(clippy::too_many_arguments)]
1735  fn resolve_package_subpath(
1736    &self,
1737    package_json: &PackageJson,
1738    package_subpath: &str,
1739    referrer: Option<&UrlOrPathRef>,
1740    resolution_mode: ResolutionMode,
1741    conditions: &[Cow<'static, str>],
1742    resolution_kind: NodeResolutionKind,
1743  ) -> Result<(MaybeTypesResolvedUrl, ResolvedMethod), PackageSubpathResolveError>
1744  {
1745    if let Some(exports) = &package_json.exports {
1746      let result = self.package_exports_resolve_internal(
1747        &package_json.path,
1748        package_subpath,
1749        exports,
1750        referrer,
1751        resolution_mode,
1752        conditions,
1753        resolution_kind,
1754      );
1755      match result {
1756        Ok(found) => return Ok((found, ResolvedMethod::PackageExports)),
1757        Err(exports_err) => {
1758          if resolution_kind.is_types() && package_subpath == "." {
1759            return self
1760              .legacy_main_resolve(
1761                package_json,
1762                referrer,
1763                resolution_mode,
1764                conditions,
1765                resolution_kind,
1766              )
1767              .map(|url| (url, ResolvedMethod::PackageSubPath))
1768              .map_err(|err| {
1769                PackageSubpathResolveErrorKind::LegacyResolve(err).into()
1770              });
1771          }
1772          return Err(
1773            PackageSubpathResolveErrorKind::Exports(exports_err).into(),
1774          );
1775        }
1776      }
1777    }
1778
1779    if package_subpath == "." {
1780      self
1781        .legacy_main_resolve(
1782          package_json,
1783          referrer,
1784          resolution_mode,
1785          conditions,
1786          resolution_kind,
1787        )
1788        .map(|url| (url, ResolvedMethod::PackageSubPath))
1789        .map_err(|err| {
1790          PackageSubpathResolveErrorKind::LegacyResolve(err).into_box()
1791        })
1792    } else {
1793      self
1794        .resolve_subpath_exact(
1795          package_json.path.parent().unwrap(),
1796          package_subpath,
1797          Some(package_json),
1798          referrer,
1799          resolution_mode,
1800          conditions,
1801          resolution_kind,
1802        )
1803        .map(|url| (url, ResolvedMethod::PackageSubPath))
1804        .map_err(|err| {
1805          PackageSubpathResolveErrorKind::LegacyResolve(err.into()).into_box()
1806        })
1807    }
1808  }
1809
1810  fn pkg_json_types_versions<'a>(
1811    &'a self,
1812    pkg_json: &'a PackageJson,
1813    resolution_kind: NodeResolutionKind,
1814  ) -> Option<TypesVersions<'a, TSys>> {
1815    if !resolution_kind.is_types() {
1816      return None;
1817    }
1818    pkg_json
1819      .types_versions
1820      .as_ref()
1821      .and_then(|entries| {
1822        let ts_version = self.resolution_config.typescript_version.as_ref()?;
1823        entries
1824          .iter()
1825          .filter_map(|(k, v)| {
1826            let version_req = VersionReq::parse_from_npm(k).ok()?;
1827            version_req.matches(ts_version).then_some(v)
1828          })
1829          .next()
1830      })
1831      .and_then(|value| value.as_object())
1832      .map(|value| TypesVersions {
1833        value,
1834        dir_path: pkg_json.dir_path(),
1835        sys: &self.sys,
1836      })
1837  }
1838
1839  #[allow(clippy::too_many_arguments)]
1840  fn resolve_subpath_exact(
1841    &self,
1842    directory: &Path,
1843    package_subpath: &str,
1844    package_json: Option<&PackageJson>,
1845    referrer: Option<&UrlOrPathRef>,
1846    resolution_mode: ResolutionMode,
1847    conditions: &[Cow<'static, str>],
1848    resolution_kind: NodeResolutionKind,
1849  ) -> Result<MaybeTypesResolvedUrl, TypesNotFoundError> {
1850    assert_ne!(package_subpath, ".");
1851    let types_versions = package_json.and_then(|pkg_json| {
1852      self.pkg_json_types_versions(pkg_json, resolution_kind)
1853    });
1854    let package_subpath = types_versions
1855      .and_then(|v| v.map(package_subpath))
1856      .unwrap_or(Cow::Borrowed(package_subpath));
1857    let file_path = directory.join(package_subpath.as_ref());
1858    self.maybe_resolve_types(
1859      LocalUrlOrPath::Path(LocalPath {
1860        path: file_path,
1861        known_exists: false,
1862      }),
1863      referrer,
1864      resolution_mode,
1865      conditions,
1866      resolution_kind,
1867    )
1868  }
1869
1870  fn resolve_package_subpath_no_pkg_json(
1871    &self,
1872    directory: &Path,
1873    package_subpath: &str,
1874    maybe_referrer: Option<&UrlOrPathRef>,
1875    resolution_mode: ResolutionMode,
1876    conditions: &[Cow<'static, str>],
1877    resolution_kind: NodeResolutionKind,
1878  ) -> Result<MaybeTypesResolvedUrl, LegacyResolveError> {
1879    if package_subpath == "." {
1880      self.legacy_index_resolve(
1881        directory,
1882        maybe_referrer,
1883        resolution_mode,
1884        resolution_kind,
1885      )
1886    } else {
1887      self
1888        .resolve_subpath_exact(
1889          directory,
1890          package_subpath,
1891          None,
1892          maybe_referrer,
1893          resolution_mode,
1894          conditions,
1895          resolution_kind,
1896        )
1897        .map_err(|err| err.into())
1898    }
1899  }
1900
1901  pub(crate) fn legacy_fallback_resolve<'a>(
1902    &self,
1903    package_json: &'a PackageJson,
1904  ) -> Option<&'a str> {
1905    fn filter_empty(value: Option<&str>) -> Option<&str> {
1906      value.map(|v| v.trim()).filter(|v| !v.is_empty())
1907    }
1908    if self.resolution_config.bundle_mode {
1909      let maybe_browser = if self.resolution_config.prefer_browser_field {
1910        filter_empty(package_json.browser.as_deref())
1911      } else {
1912        None
1913      };
1914      maybe_browser
1915        .or(filter_empty(package_json.module.as_deref()))
1916        .or(filter_empty(package_json.main.as_deref()))
1917    } else {
1918      filter_empty(package_json.main.as_deref())
1919    }
1920  }
1921
1922  fn legacy_main_resolve(
1923    &self,
1924    package_json: &PackageJson,
1925    maybe_referrer: Option<&UrlOrPathRef>,
1926    resolution_mode: ResolutionMode,
1927    conditions: &[Cow<'static, str>],
1928    resolution_kind: NodeResolutionKind,
1929  ) -> Result<MaybeTypesResolvedUrl, LegacyResolveError> {
1930    let maybe_main = if resolution_kind.is_types() {
1931      match package_json.types.as_ref() {
1932        Some(types) => {
1933          let types_versions =
1934            self.pkg_json_types_versions(package_json, resolution_kind);
1935          Some(
1936            types_versions
1937              .and_then(|v| v.map(types.as_ref()))
1938              .unwrap_or(Cow::Borrowed(types.as_str())),
1939          )
1940        }
1941        None => {
1942          // fallback to checking the main entrypoint for
1943          // a corresponding declaration file
1944          if let Some(main) = self.legacy_fallback_resolve(package_json) {
1945            let main = package_json.path.parent().unwrap().join(main).clean();
1946            let decl_path_result = self.path_to_declaration_path(
1947              LocalPath {
1948                path: main,
1949                known_exists: false,
1950              },
1951              maybe_referrer,
1952              resolution_mode,
1953              conditions,
1954            );
1955            // don't surface errors, fallback to checking the index now
1956            if let Ok(url_or_path) = decl_path_result {
1957              return Ok(url_or_path);
1958            }
1959          }
1960          None
1961        }
1962      }
1963    } else {
1964      self
1965        .legacy_fallback_resolve(package_json)
1966        .map(Cow::Borrowed)
1967    };
1968
1969    if let Some(main) = maybe_main.as_deref() {
1970      let guess = package_json.path.parent().unwrap().join(main).clean();
1971      if self.sys.is_file(&guess) {
1972        return Ok(self.maybe_resolve_types(
1973          LocalUrlOrPath::Path(LocalPath {
1974            path: guess,
1975            known_exists: true,
1976          }),
1977          maybe_referrer,
1978          resolution_mode,
1979          conditions,
1980          resolution_kind,
1981        )?);
1982      }
1983
1984      // todo(dsherret): investigate exactly how node and typescript handles this
1985      let endings = if resolution_kind.is_types() {
1986        match resolution_mode {
1987          ResolutionMode::Require => {
1988            vec![".d.ts", ".d.cts", "/index.d.ts", "/index.d.cts"]
1989          }
1990          ResolutionMode::Import => vec![
1991            ".d.ts",
1992            ".d.mts",
1993            "/index.d.ts",
1994            "/index.d.mts",
1995            ".d.cts",
1996            "/index.d.cts",
1997          ],
1998        }
1999      } else {
2000        vec![".js", "/index.js"]
2001      };
2002      for ending in endings {
2003        let guess = package_json
2004          .path
2005          .parent()
2006          .unwrap()
2007          .join(format!("{main}{ending}"))
2008          .clean();
2009        if self.sys.is_file(&guess) {
2010          // TODO(bartlomieju): emitLegacyIndexDeprecation()
2011          return Ok(MaybeTypesResolvedUrl(LocalUrlOrPath::Path(LocalPath {
2012            path: guess,
2013            known_exists: true,
2014          })));
2015        }
2016      }
2017    }
2018
2019    self.legacy_index_resolve(
2020      package_json.path.parent().unwrap(),
2021      maybe_referrer,
2022      resolution_mode,
2023      resolution_kind,
2024    )
2025  }
2026
2027  fn legacy_index_resolve(
2028    &self,
2029    directory: &Path,
2030    maybe_referrer: Option<&UrlOrPathRef>,
2031    resolution_mode: ResolutionMode,
2032    resolution_kind: NodeResolutionKind,
2033  ) -> Result<MaybeTypesResolvedUrl, LegacyResolveError> {
2034    let index_file_names = if resolution_kind.is_types() {
2035      // todo(dsherret): investigate exactly how typescript does this
2036      match resolution_mode {
2037        ResolutionMode::Require => vec!["index.d.ts", "index.d.cts"],
2038        ResolutionMode::Import => {
2039          vec!["index.d.ts", "index.d.mts", "index.d.cts"]
2040        }
2041      }
2042    } else {
2043      // Node only resolves index.js and not index.cjs/mjs
2044      vec!["index.js"]
2045    };
2046    for index_file_name in index_file_names {
2047      let guess = directory.join(index_file_name).clean();
2048      if self.sys.is_file(&guess) {
2049        // TODO(bartlomieju): emitLegacyIndexDeprecation()
2050        return Ok(MaybeTypesResolvedUrl(LocalUrlOrPath::Path(LocalPath {
2051          path: guess,
2052          known_exists: true,
2053        })));
2054      }
2055    }
2056
2057    if resolution_kind.is_types() {
2058      Err(
2059        TypesNotFoundError(Box::new(TypesNotFoundErrorData {
2060          code_specifier: UrlOrPathRef::from_path(&directory.join("index.js"))
2061            .display(),
2062          maybe_referrer: maybe_referrer.map(|r| r.display()),
2063        }))
2064        .into(),
2065      )
2066    } else {
2067      Err(
2068        ModuleNotFoundError {
2069          specifier: UrlOrPath::Path(directory.join("index.js")),
2070          maybe_referrer: maybe_referrer.map(|r| r.display()),
2071          suggested_ext: None,
2072        }
2073        .into(),
2074      )
2075    }
2076  }
2077
2078  fn resolve_types_package_folder_from_package_json(
2079    &self,
2080    pkg_json: &PackageJson,
2081    maybe_referrer: Option<&UrlOrPathRef>,
2082  ) -> Option<PathBuf> {
2083    let package_name = pkg_json.name.as_deref()?;
2084    let maybe_version = pkg_json
2085      .version
2086      .as_ref()
2087      .and_then(|v| Version::parse_from_npm(v).ok());
2088    self.resolve_types_package_folder_with_name_and_version(
2089      package_name,
2090      maybe_version.as_ref(),
2091      maybe_referrer,
2092    )
2093  }
2094
2095  fn resolve_types_package_folder_with_name_and_version(
2096    &self,
2097    package_name: &str,
2098    maybe_version: Option<&Version>,
2099    maybe_referrer: Option<&UrlOrPathRef>,
2100  ) -> Option<PathBuf> {
2101    let types_package_name = types_package_name(package_name)?;
2102    log::debug!(
2103      "Attempting to resolve types package '{}@{}'.",
2104      types_package_name,
2105      maybe_version
2106        .as_ref()
2107        .map(|s| s.to_string())
2108        .as_deref()
2109        .unwrap_or("*")
2110    );
2111    self.npm_pkg_folder_resolver.resolve_types_package_folder(
2112      &types_package_name,
2113      maybe_version,
2114      maybe_referrer,
2115    )
2116  }
2117
2118  /// Resolves a specifier that is pointing into a node_modules folder by canonicalizing it.
2119  ///
2120  /// Returns `None` when the specifier is not in a node_modules folder.
2121  pub fn handle_if_in_node_modules(&self, specifier: &Url) -> Option<Url> {
2122    // skip canonicalizing if we definitely know it's unnecessary
2123    if specifier.scheme() == "file"
2124      && specifier.path().contains("/node_modules/")
2125    {
2126      // Specifiers in the node_modules directory are canonicalized
2127      // so canonicalize then check if it's in the node_modules directory.
2128      let specifier = resolve_specifier_into_node_modules(&self.sys, specifier);
2129      return Some(specifier);
2130    }
2131
2132    None
2133  }
2134
2135  /// Ex. returns `fs` for `node:fs`
2136  fn get_module_name_from_builtin_node_module_url<'url>(
2137    &self,
2138    url: &'url Url,
2139  ) -> Result<Option<&'url str>, UnknownBuiltInNodeModuleError> {
2140    if url.scheme() != "node" {
2141      return Ok(None);
2142    }
2143
2144    let module_name = url.path();
2145
2146    if !self
2147      .is_built_in_node_module_checker
2148      .is_builtin_node_module(module_name)
2149    {
2150      return Err(UnknownBuiltInNodeModuleError {
2151        module_name: module_name.to_string(),
2152      });
2153    }
2154    Ok(Some(module_name))
2155  }
2156}
2157
2158struct ResolvedPkgJsonImport<'a> {
2159  pub target: &'a serde_json::Value,
2160  pub sub_path: &'a str,
2161  pub package_sub_path: &'a str,
2162  pub is_pattern: bool,
2163}
2164
2165fn resolve_pkg_json_import<'a>(
2166  pkg_json: &'a PackageJson,
2167  name: &'a str,
2168) -> Option<ResolvedPkgJsonImport<'a>> {
2169  let imports = pkg_json.imports.as_ref()?;
2170  if let Some((name, target)) =
2171    imports.get_key_value(name).filter(|_| !name.contains('*'))
2172  {
2173    Some(ResolvedPkgJsonImport {
2174      target,
2175      sub_path: "",
2176      package_sub_path: name,
2177      is_pattern: false,
2178    })
2179  } else {
2180    let mut best_match: &'a str = "";
2181    let mut best_match_subpath: &'a str = "";
2182    for key in imports.keys() {
2183      let pattern_index = key.find('*');
2184      if let Some(pattern_index) = pattern_index {
2185        let key_sub = &key[0..pattern_index];
2186        if name.starts_with(key_sub) {
2187          let pattern_trailer = &key[pattern_index + 1..];
2188          if name.len() > key.len()
2189            && name.ends_with(&pattern_trailer)
2190            && pattern_key_compare(best_match, key) == 1
2191            && key.rfind('*') == Some(pattern_index)
2192          {
2193            best_match = key;
2194            best_match_subpath =
2195              &name[pattern_index..(name.len() - pattern_trailer.len())];
2196          }
2197        }
2198      }
2199    }
2200
2201    if !best_match.is_empty() {
2202      let target = imports.get(best_match).unwrap();
2203      Some(ResolvedPkgJsonImport {
2204        target,
2205        sub_path: best_match_subpath,
2206        package_sub_path: best_match,
2207        is_pattern: true,
2208      })
2209    } else {
2210      None
2211    }
2212  }
2213}
2214
2215fn bin_value_from_file<TSys: FsOpen>(
2216  path: &Path,
2217  sys: &NodeResolutionSys<TSys>,
2218) -> Option<BinValue> {
2219  let mut file = match sys.fs_open(path, OpenOptions::new().read()) {
2220    Ok(file) => file,
2221    Err(err) => {
2222      if err.kind() == std::io::ErrorKind::NotFound {
2223        return None;
2224      }
2225      log::debug!(
2226        "Failed to open bin file '{}': {:#}; treating as executable",
2227        path.display(),
2228        err,
2229      );
2230      return Some(BinValue::Executable(path.to_path_buf()));
2231    }
2232  };
2233  let mut buf = [0; 4];
2234  let (is_binary, buf): (bool, &[u8]) = {
2235    let result = file.read_exact(&mut buf);
2236    if let Err(err) = result {
2237      log::debug!("Failed to read binary file '{}': {:#}", path.display(), err);
2238      // safer fallback to assume it's a binary
2239      (true, &[])
2240    } else {
2241      (
2242        is_binary(&buf) || (!(buf[0] == b'#' && buf[1] == b'!')),
2243        &buf[..],
2244      )
2245    }
2246  };
2247
2248  if is_binary {
2249    return Some(BinValue::Executable(path.to_path_buf()));
2250  }
2251  let mut buf_read = BufReader::new(file);
2252  let mut contents = Vec::new();
2253  contents.extend_from_slice(buf);
2254  if let Ok(len) = buf_read.read_to_end(&mut contents)
2255    && len > 0
2256    && let Ok(contents) = String::from_utf8(contents)
2257    && let Some(path) =
2258      resolve_execution_path_from_npx_shim(Cow::Borrowed(path), &contents)
2259  {
2260    return Some(BinValue::JsFile(path));
2261  }
2262
2263  Some(BinValue::Executable(path.to_path_buf()))
2264}
2265
2266/// This is not ideal, but it works ok because it allows us to bypass
2267/// the shebang and execute the script directly with Deno.
2268fn resolve_execution_path_from_npx_shim(
2269  file_path: Cow<Path>,
2270  text: &str,
2271) -> Option<PathBuf> {
2272  static SCRIPT_PATH_RE: Lazy<Regex> =
2273    lazy_regex::lazy_regex!(r#"exec\s+node\s+"\$basedir\/([^"]+)" "\$@""#);
2274
2275  let maybe_first_line = {
2276    let index = text.find("\n")?;
2277    Some(&text[0..index])
2278  };
2279
2280  if let Some(first_line) = maybe_first_line {
2281    // NOTE(bartlomieju): this is not perfect, but handle two most common scenarios
2282    // where Node is run without any args.
2283    if first_line == "#!/usr/bin/env node"
2284      || first_line == "#!/usr/bin/env -S node"
2285    {
2286      // launch this file itself because it's a JS file
2287      return Some(file_path.into_owned());
2288    }
2289  }
2290
2291  // Search for...
2292  // > "$basedir/../next/dist/bin/next" "$@"
2293  // ...which is what it will look like on Windows
2294  SCRIPT_PATH_RE
2295    .captures(text)
2296    .and_then(|c| c.get(1))
2297    .map(|relative_path| {
2298      file_path.parent().unwrap().join(relative_path.as_str())
2299    })
2300}
2301
2302fn resolve_bin_entry_value<'a>(
2303  package_json: &PackageJson,
2304  bins: &'a BTreeMap<String, BinValue>,
2305  bin_name: Option<&str>,
2306) -> Result<&'a BinValue, AnyError> {
2307  if bins.is_empty() {
2308    bail!(
2309      "'{}' did not have a bin property with a string or non-empty object value",
2310      package_json.path.display()
2311    );
2312  }
2313  let default_bin = package_json.resolve_default_bin_name().ok();
2314  let searching_bin = bin_name.or(default_bin);
2315  match searching_bin.and_then(|bin_name| bins.get(bin_name)) {
2316    Some(bin) => Ok(bin),
2317    _ => {
2318      if bins.len() > 1
2319        && let Some(first) = bins.values().next()
2320        && bins.values().all(|bin| bin == first)
2321      {
2322        return Ok(first);
2323      }
2324      if bin_name.is_none()
2325        && bins.len() == 1
2326        && let Some(first) = bins.values().next()
2327      {
2328        return Ok(first);
2329      }
2330      let default_bin = package_json.resolve_default_bin_name().ok();
2331      let prefix = package_json
2332        .name
2333        .as_ref()
2334        .map(|n| {
2335          let mut prefix = format!("npm:{}", n);
2336          if let Some(version) = &package_json.version {
2337            prefix.push('@');
2338            prefix.push_str(version);
2339          }
2340          prefix
2341        })
2342        .unwrap_or_default();
2343      let keys = bins
2344        .keys()
2345        .map(|k| {
2346          if prefix.is_empty() {
2347            format!(" * {k}")
2348          } else if Some(k.as_str()) == default_bin {
2349            format!(" * {prefix}")
2350          } else {
2351            format!(" * {prefix}/{k}")
2352          }
2353        })
2354        .collect::<Vec<_>>();
2355      bail!(
2356        "'{}' did not have a bin entry for '{}'{}",
2357        package_json.path.display(),
2358        searching_bin.unwrap_or("<unspecified>"),
2359        if keys.is_empty() {
2360          "".to_string()
2361        } else {
2362          format!("\n\nPossibilities:\n{}", keys.join("\n"))
2363        }
2364      )
2365    }
2366  }
2367}
2368
2369fn should_be_treated_as_relative_or_absolute_path(specifier: &str) -> bool {
2370  if specifier.is_empty() {
2371    return false;
2372  }
2373
2374  if specifier.starts_with('/') {
2375    return true;
2376  }
2377
2378  deno_path_util::is_relative_specifier(specifier)
2379}
2380
2381/// Alternate `PathBuf::with_extension` that will handle known extensions
2382/// more intelligently.
2383fn with_known_extension(path: &Path, ext: &str) -> PathBuf {
2384  const NON_DECL_EXTS: &[&str] = &[
2385    "cjs", "js", "json", "jsx", "mjs", "tsx", /* ex. types.d */ "d",
2386  ];
2387  const DECL_EXTS: &[&str] = &["cts", "mts", "ts"];
2388
2389  let file_name = match path.file_name() {
2390    Some(value) => value.to_string_lossy(),
2391    None => return path.to_path_buf(),
2392  };
2393  let lowercase_file_name = file_name.to_lowercase();
2394  let period_index = lowercase_file_name.rfind('.').and_then(|period_index| {
2395    let ext = &lowercase_file_name[period_index + 1..];
2396    if DECL_EXTS.contains(&ext) {
2397      if let Some(next_period_index) =
2398        lowercase_file_name[..period_index].rfind('.')
2399      {
2400        if &lowercase_file_name[next_period_index + 1..period_index] == "d" {
2401          Some(next_period_index)
2402        } else {
2403          Some(period_index)
2404        }
2405      } else {
2406        Some(period_index)
2407      }
2408    } else if NON_DECL_EXTS.contains(&ext) {
2409      Some(period_index)
2410    } else {
2411      None
2412    }
2413  });
2414
2415  let file_name = match period_index {
2416    Some(period_index) => &file_name[..period_index],
2417    None => &file_name,
2418  };
2419  path.with_file_name(format!("{file_name}.{ext}"))
2420}
2421
2422fn to_specifier_display_string(url: &UrlOrPathRef) -> String {
2423  if let Ok(path) = url.path() {
2424    path.display().to_string()
2425  } else {
2426    url.display().to_string()
2427  }
2428}
2429
2430fn throw_invalid_subpath(
2431  subpath: String,
2432  package_json_path: &Path,
2433  internal: bool,
2434  maybe_referrer: Option<&UrlOrPathRef>,
2435) -> InvalidModuleSpecifierError {
2436  let ie = if internal { "imports" } else { "exports" };
2437  let reason = format!(
2438    "request is not a valid subpath for the \"{}\" resolution of {}",
2439    ie,
2440    package_json_path.display(),
2441  );
2442  InvalidModuleSpecifierError {
2443    request: subpath,
2444    reason: Cow::Owned(reason),
2445    maybe_referrer: maybe_referrer.map(to_specifier_display_string),
2446  }
2447}
2448
2449pub fn parse_npm_pkg_name<'a>(
2450  specifier: &'a str,
2451  referrer: &UrlOrPathRef,
2452) -> Result<(&'a str, Cow<'static, str>, bool), InvalidModuleSpecifierError> {
2453  let mut separator_index = specifier.find('/');
2454  let mut valid_package_name = true;
2455  let mut is_scoped = false;
2456  if specifier.is_empty() {
2457    valid_package_name = false;
2458  } else if specifier.starts_with('@') {
2459    is_scoped = true;
2460    if let Some(index) = separator_index {
2461      separator_index = specifier[index + 1..]
2462        .find('/')
2463        .map(|new_index| index + 1 + new_index);
2464    } else {
2465      valid_package_name = false;
2466    }
2467  }
2468
2469  let (package_name, subpath) = if let Some(index) = separator_index {
2470    let (package_name, subpath) = specifier.split_at(index);
2471    (package_name, Cow::Owned(format!(".{}", subpath)))
2472  } else {
2473    (specifier, Cow::Borrowed("."))
2474  };
2475
2476  // Package name cannot have leading . and cannot have percent-encoding or separators.
2477  for ch in package_name.chars() {
2478    if ch == '%' || ch == '\\' {
2479      valid_package_name = false;
2480      break;
2481    }
2482  }
2483
2484  if !valid_package_name {
2485    return Err(errors::InvalidModuleSpecifierError {
2486      request: specifier.to_string(),
2487      reason: Cow::Borrowed("is not a valid package name"),
2488      maybe_referrer: Some(to_specifier_display_string(referrer)),
2489    });
2490  }
2491
2492  Ok((package_name, subpath, is_scoped))
2493}
2494
2495/// Resolves a specifier that is pointing into a node_modules folder.
2496///
2497/// Note: This should be called whenever getting the specifier from
2498/// a Module::External(module) reference because that module might
2499/// not be fully resolved at the time deno_graph is analyzing it
2500/// because the node_modules folder might not exist at that time.
2501pub fn resolve_specifier_into_node_modules(
2502  sys: &impl FsCanonicalize,
2503  specifier: &Url,
2504) -> Url {
2505  deno_path_util::url_to_file_path(specifier)
2506    .ok()
2507    // this path might not exist at the time the graph is being created
2508    // because the node_modules folder might not yet exist
2509    .and_then(|path| {
2510      deno_path_util::fs::canonicalize_path_maybe_not_exists(sys, &path).ok()
2511    })
2512    .and_then(|path| deno_path_util::url_from_file_path(&path).ok())
2513    .unwrap_or_else(|| specifier.clone())
2514}
2515
2516fn pattern_key_compare(a: &str, b: &str) -> i32 {
2517  let a_pattern_index = a.find('*');
2518  let b_pattern_index = b.find('*');
2519
2520  let base_len_a = if let Some(index) = a_pattern_index {
2521    index + 1
2522  } else {
2523    a.len()
2524  };
2525  let base_len_b = if let Some(index) = b_pattern_index {
2526    index + 1
2527  } else {
2528    b.len()
2529  };
2530
2531  if base_len_a > base_len_b {
2532    return -1;
2533  }
2534
2535  if base_len_b > base_len_a {
2536    return 1;
2537  }
2538
2539  if a_pattern_index.is_none() {
2540    return 1;
2541  }
2542
2543  if b_pattern_index.is_none() {
2544    return -1;
2545  }
2546
2547  if a.len() > b.len() {
2548    return -1;
2549  }
2550
2551  if b.len() > a.len() {
2552    return 1;
2553  }
2554
2555  0
2556}
2557
2558/// Gets the corresponding @types package for the provided package name
2559/// returning `None` when the package is already a @types package.
2560pub fn types_package_name(package_name: &str) -> Option<String> {
2561  if package_name.starts_with("@types/") {
2562    return None;
2563  }
2564  // Scoped packages will get two underscores for each slash
2565  // https://github.com/DefinitelyTyped/DefinitelyTyped/tree/15f1ece08f7b498f4b9a2147c2a46e94416ca777#what-about-scoped-packages
2566  capacity_builder::StringBuilder::build(|builder| {
2567    builder.append("@types/");
2568    for (i, c) in package_name.chars().enumerate() {
2569      match c {
2570        '@' if i == 0 => {
2571          // ignore
2572        }
2573        '/' => {
2574          builder.append("__");
2575        }
2576        c => builder.append(c),
2577      }
2578    }
2579  })
2580  .ok()
2581}
2582
2583/// Node is more lenient joining paths than the url crate is,
2584/// so this function handles that.
2585fn node_join_url(url: &Url, path: &str) -> Result<Url, url::ParseError> {
2586  if let Some(suffix) = path.strip_prefix(".//") {
2587    // specifier had two leading slashes
2588    url.join(&format!("./{}", suffix))
2589  } else {
2590    url.join(path)
2591  }
2592}
2593
2594struct TypesVersions<'a, TSys: FsMetadata> {
2595  dir_path: &'a Path,
2596  value: &'a serde_json::Map<std::string::String, serde_json::Value>,
2597  sys: &'a NodeResolutionSys<TSys>,
2598}
2599
2600impl<'a, TSys: FsMetadata> TypesVersions<'a, TSys> {
2601  pub fn map(&self, search: &str) -> Option<Cow<'a, str>> {
2602    let mut search = search
2603      .strip_prefix("./")
2604      .unwrap_or(search)
2605      .trim_matches('/');
2606    for (key, value) in self.value {
2607      let key = key.strip_suffix("./").unwrap_or(key).trim_matches('/');
2608      let is_match = if key == "*" || key == search {
2609        true
2610      } else if let Some(key_prefix) = key.strip_suffix("/*") {
2611        if let Some(new_search) = search.strip_prefix(key_prefix) {
2612          search = new_search.trim_matches('/');
2613          true
2614        } else {
2615          false
2616        }
2617      } else {
2618        false
2619      };
2620      if !is_match {
2621        continue;
2622      }
2623      if let Some(values) = value.as_array() {
2624        for value in values.iter().filter_map(|s| s.as_str()) {
2625          let value = if let Some(asterisk_index) = value.find('*') {
2626            Cow::Owned(format!(
2627              "{}{}{}",
2628              &value[..asterisk_index],
2629              search,
2630              &value[asterisk_index + 1..]
2631            ))
2632          } else {
2633            Cow::Borrowed(value)
2634          };
2635          let path = self.dir_path.join(value.as_ref());
2636          if self.sys.is_file(&path) {
2637            return Some(value);
2638          }
2639        }
2640      }
2641    }
2642    None
2643  }
2644}
2645
2646#[derive(Debug, Clone, PartialEq, Eq)]
2647pub enum BinValue {
2648  JsFile(PathBuf),
2649  Executable(PathBuf),
2650}
2651
2652impl BinValue {
2653  pub fn path(&self) -> &Path {
2654    match self {
2655      BinValue::JsFile(path) => path,
2656      BinValue::Executable(path) => path,
2657    }
2658  }
2659}
2660fn is_binary(data: &[u8]) -> bool {
2661  is_elf(data) || is_macho(data) || is_pe(data)
2662}
2663
2664// vendored from libsui because they're super small
2665/// Check if the given data is an ELF64 binary
2666fn is_elf(data: &[u8]) -> bool {
2667  if data.len() < 4 {
2668    return false;
2669  }
2670  let magic = u32::from_be_bytes([data[0], data[1], data[2], data[3]]);
2671  magic == 0x7f454c46
2672}
2673
2674/// Check if the given data is a 64-bit Mach-O binary
2675fn is_macho(data: &[u8]) -> bool {
2676  if data.len() < 4 {
2677    return false;
2678  }
2679  let magic = u32::from_le_bytes([data[0], data[1], data[2], data[3]]);
2680  magic == 0xfeedfacf
2681}
2682
2683/// Check if the given data is a PE32+ binary
2684fn is_pe(data: &[u8]) -> bool {
2685  if data.len() < 2 {
2686    return false;
2687  }
2688  let magic = u16::from_le_bytes([data[0], data[1]]);
2689  magic == 0x5a4d
2690}
2691
2692#[cfg(test)]
2693mod tests {
2694  use deno_package_json::PackageJsonBins;
2695  use serde_json::json;
2696  use sys_traits::FsCreateDirAll;
2697  use sys_traits::FsWrite;
2698  use sys_traits::impls::InMemorySys;
2699
2700  use super::*;
2701
2702  fn build_package_json(json: Value) -> PackageJson {
2703    PackageJson::load_from_value(PathBuf::from("/package.json"), json).unwrap()
2704  }
2705
2706  fn resolve_bins(package_json: &PackageJson) -> BTreeMap<String, BinValue> {
2707    match package_json.resolve_bins().unwrap() {
2708      PackageJsonBins::Directory(_) => unreachable!(),
2709      PackageJsonBins::Bins(bins) => bins
2710        .into_iter()
2711        .map(|(k, v)| (k, BinValue::JsFile(v)))
2712        .collect(),
2713    }
2714  }
2715
2716  #[test]
2717  fn test_resolve_bin_entry_value() {
2718    // should resolve the specified value
2719    {
2720      let pkg_json = build_package_json(json!({
2721        "name": "pkg",
2722        "version": "1.1.1",
2723        "bin": {
2724          "bin1": "./value1",
2725          "bin2": "./value2",
2726          "pkg": "./value3",
2727        }
2728      }));
2729      let bins = resolve_bins(&pkg_json);
2730      assert_eq!(
2731        resolve_bin_entry_value(&pkg_json, &bins, Some("bin1"))
2732          .unwrap()
2733          .path(),
2734        pkg_json.dir_path().join("./value1")
2735      );
2736      assert_eq!(
2737        resolve_bin_entry_value(&pkg_json, &bins, Some("pkg"))
2738          .unwrap()
2739          .path(),
2740        pkg_json.dir_path().join("./value3")
2741      );
2742
2743      // should not resolve when specified value does not exist
2744      assert_eq!(
2745        resolve_bin_entry_value(&pkg_json, &bins, Some("other"))
2746          .err()
2747          .unwrap()
2748          .to_string(),
2749        concat!(
2750          "'/package.json' did not have a bin entry for 'other'\n",
2751          "\n",
2752          "Possibilities:\n",
2753          " * npm:pkg@1.1.1/bin1\n",
2754          " * npm:pkg@1.1.1/bin2\n",
2755          " * npm:pkg@1.1.1"
2756        )
2757      );
2758    }
2759
2760    // should not resolve when default value can't be determined
2761    {
2762      let pkg_json = build_package_json(json!({
2763        "name": "pkg",
2764        "version": "1.1.1",
2765        "bin": {
2766          "bin": "./value1",
2767          "bin2": "./value2",
2768        }
2769      }));
2770      let bins = resolve_bins(&pkg_json);
2771      assert_eq!(
2772        resolve_bin_entry_value(&pkg_json, &bins, Some("pkg"))
2773          .err()
2774          .unwrap()
2775          .to_string(),
2776        concat!(
2777          "'/package.json' did not have a bin entry for 'pkg'\n",
2778          "\n",
2779          "Possibilities:\n",
2780          " * npm:pkg@1.1.1/bin\n",
2781          " * npm:pkg@1.1.1/bin2",
2782        )
2783      );
2784    }
2785
2786    // should resolve since all the values are the same
2787    {
2788      let pkg_json = build_package_json(json!({
2789        "name": "pkg",
2790        "version": "1.2.3",
2791        "bin": {
2792          "bin1": "./value",
2793          "bin2": "./value",
2794        }
2795      }));
2796      let bins = resolve_bins(&pkg_json);
2797      assert_eq!(
2798        resolve_bin_entry_value(&pkg_json, &bins, Some("pkg"))
2799          .unwrap()
2800          .path(),
2801        pkg_json.dir_path().join("./value")
2802      );
2803    }
2804
2805    // should resolve when not specified and only one value
2806    {
2807      let pkg_json = build_package_json(json!({
2808        "name": "pkg",
2809        "version": "1.2.3",
2810        "bin": {
2811          "something": "./value",
2812        }
2813      }));
2814      let bins = resolve_bins(&pkg_json);
2815      assert_eq!(
2816        resolve_bin_entry_value(&pkg_json, &bins, None)
2817          .unwrap()
2818          .path(),
2819        pkg_json.dir_path().join("./value")
2820      );
2821    }
2822
2823    // should not resolve when specified and is a string
2824    {
2825      let pkg_json = build_package_json(json!({
2826        "name": "pkg",
2827        "version": "1.2.3",
2828        "bin": "./value",
2829      }));
2830      let bins = resolve_bins(&pkg_json);
2831      assert_eq!(
2832        resolve_bin_entry_value(&pkg_json, &bins, Some("path"))
2833          .err()
2834          .unwrap()
2835          .to_string(),
2836        concat!(
2837          "'/package.json' did not have a bin entry for 'path'\n",
2838          "\n",
2839          "Possibilities:\n",
2840          " * npm:pkg@1.2.3"
2841        )
2842      );
2843    }
2844
2845    // no version in the package.json
2846    {
2847      let pkg_json = build_package_json(json!({
2848        "name": "pkg",
2849        "bin": {
2850          "bin1": "./value1",
2851          "bin2": "./value2",
2852        }
2853      }));
2854      let bins = resolve_bins(&pkg_json);
2855      assert_eq!(
2856        resolve_bin_entry_value(&pkg_json, &bins, Some("pkg"))
2857          .err()
2858          .unwrap()
2859          .to_string(),
2860        concat!(
2861          "'/package.json' did not have a bin entry for 'pkg'\n",
2862          "\n",
2863          "Possibilities:\n",
2864          " * npm:pkg/bin1\n",
2865          " * npm:pkg/bin2",
2866        )
2867      );
2868    }
2869
2870    // no name or version in the package.json
2871    {
2872      let pkg_json = build_package_json(json!({
2873        "bin": {
2874          "bin1": "./value1",
2875          "bin2": "./value2",
2876        }
2877      }));
2878      let bins = resolve_bins(&pkg_json);
2879      assert_eq!(
2880        resolve_bin_entry_value(&pkg_json, &bins, Some("bin"))
2881          .err()
2882          .unwrap()
2883          .to_string(),
2884        concat!(
2885          "'/package.json' did not have a bin entry for 'bin'\n",
2886          "\n",
2887          "Possibilities:\n",
2888          " * bin1\n",
2889          " * bin2",
2890        )
2891      );
2892    }
2893  }
2894
2895  #[test]
2896  fn test_parse_npm_pkg_name() {
2897    let dummy_referrer = Url::parse("http://example.com").unwrap();
2898    let dummy_referrer = UrlOrPathRef::from_url(&dummy_referrer);
2899
2900    assert_eq!(
2901      parse_npm_pkg_name("fetch-blob", &dummy_referrer).unwrap(),
2902      ("fetch-blob", Cow::Borrowed("."), false)
2903    );
2904    assert_eq!(
2905      parse_npm_pkg_name("@vue/plugin-vue", &dummy_referrer).unwrap(),
2906      ("@vue/plugin-vue", Cow::Borrowed("."), true)
2907    );
2908    assert_eq!(
2909      parse_npm_pkg_name("@astrojs/prism/dist/highlighter", &dummy_referrer)
2910        .unwrap(),
2911      (
2912        "@astrojs/prism",
2913        Cow::Owned("./dist/highlighter".to_string()),
2914        true
2915      )
2916    );
2917  }
2918
2919  #[test]
2920  fn test_with_known_extension() {
2921    let cases = &[
2922      ("test", "d.ts", "test.d.ts"),
2923      ("test.d.ts", "ts", "test.ts"),
2924      ("test.worker", "d.ts", "test.worker.d.ts"),
2925      ("test.d.mts", "js", "test.js"),
2926    ];
2927    for (path, ext, expected) in cases {
2928      let actual = with_known_extension(Path::new(path), ext);
2929      assert_eq!(actual.to_string_lossy(), *expected);
2930    }
2931  }
2932
2933  #[test]
2934  fn test_types_package_name() {
2935    assert_eq!(types_package_name("name").unwrap(), "@types/name");
2936    assert_eq!(
2937      types_package_name("@scoped/package").unwrap(),
2938      "@types/scoped__package"
2939    );
2940    assert_eq!(types_package_name("@types/node"), None);
2941  }
2942
2943  #[test]
2944  fn test_types_versions() {
2945    let dir_path = PathBuf::from("/dir");
2946    let sys = InMemorySys::default();
2947    sys.fs_create_dir_all(dir_path.join("ts3.1")).unwrap();
2948    sys.fs_write(dir_path.join("file.d.ts"), "").unwrap();
2949    sys.fs_write(dir_path.join("ts3.1/file.d.ts"), "").unwrap();
2950    sys.fs_write(dir_path.join("ts3.1/file2.d.ts"), "").unwrap();
2951    let node_resolution_sys = NodeResolutionSys::new(sys, None);
2952
2953    // asterisk key
2954    {
2955      let value = serde_json::json!({
2956        "*": ["ts3.1/*"]
2957      });
2958      let types_versions = TypesVersions {
2959        dir_path: &dir_path,
2960        value: value.as_object().unwrap(),
2961        sys: &node_resolution_sys,
2962      };
2963      assert_eq!(types_versions.map("file.d.ts").unwrap(), "ts3.1/file.d.ts");
2964      assert_eq!(
2965        types_versions.map("file2.d.ts").unwrap(),
2966        "ts3.1/file2.d.ts"
2967      );
2968      assert!(types_versions.map("non_existent/file.d.ts").is_none());
2969    }
2970    // specific file
2971    {
2972      let value = serde_json::json!({
2973        "types.d.ts": ["ts3.1/file.d.ts"]
2974      });
2975      let types_versions = TypesVersions {
2976        dir_path: &dir_path,
2977        value: value.as_object().unwrap(),
2978        sys: &node_resolution_sys,
2979      };
2980      assert_eq!(types_versions.map("types.d.ts").unwrap(), "ts3.1/file.d.ts");
2981      assert!(types_versions.map("file2.d.ts").is_none());
2982    }
2983    // multiple specific files
2984    {
2985      let value = serde_json::json!({
2986        "types.d.ts": ["ts3.1/file.d.ts"],
2987        "other.d.ts": ["ts3.1/file2.d.ts"],
2988      });
2989      let types_versions = TypesVersions {
2990        dir_path: &dir_path,
2991        value: value.as_object().unwrap(),
2992        sys: &node_resolution_sys,
2993      };
2994      assert_eq!(types_versions.map("types.d.ts").unwrap(), "ts3.1/file.d.ts");
2995      assert_eq!(
2996        types_versions.map("other.d.ts").unwrap(),
2997        "ts3.1/file2.d.ts"
2998      );
2999      assert!(types_versions.map("file2.d.ts").is_none());
3000    }
3001    // existing fallback
3002    {
3003      let value = serde_json::json!({
3004        "*": ["ts3.1/*", "ts3.1/file2.d.ts"]
3005      });
3006      let types_versions = TypesVersions {
3007        dir_path: &dir_path,
3008        value: value.as_object().unwrap(),
3009        sys: &node_resolution_sys,
3010      };
3011      assert_eq!(
3012        types_versions.map("testing/types.d.ts").unwrap(),
3013        "ts3.1/file2.d.ts"
3014      );
3015    }
3016    // text then asterisk in key
3017    {
3018      let value = serde_json::json!({
3019        "sub/*": ["ts3.1/file.d.ts"]
3020      });
3021      let types_versions = TypesVersions {
3022        dir_path: &dir_path,
3023        value: value.as_object().unwrap(),
3024        sys: &node_resolution_sys,
3025      };
3026      assert_eq!(
3027        types_versions.map("sub/types.d.ts").unwrap(),
3028        "ts3.1/file.d.ts"
3029      );
3030    }
3031    // text then asterisk in key and asterisk in value
3032    {
3033      let value = serde_json::json!({
3034        "sub/*": ["ts3.1/*"]
3035      });
3036      let types_versions = TypesVersions {
3037        dir_path: &dir_path,
3038        value: value.as_object().unwrap(),
3039        sys: &node_resolution_sys,
3040      };
3041      assert_eq!(
3042        types_versions.map("sub/file.d.ts").unwrap(),
3043        "ts3.1/file.d.ts"
3044      );
3045    }
3046  }
3047
3048  #[test]
3049  fn test_resolve_execution_path_from_npx_shim() {
3050    // example shim on unix
3051    let unix_shim = r#"#!/usr/bin/env node
3052"use strict";
3053console.log('Hi!');
3054"#;
3055    let path = PathBuf::from("/node_modules/.bin/example");
3056    assert_eq!(
3057      resolve_execution_path_from_npx_shim(Cow::Borrowed(&path), unix_shim)
3058        .unwrap(),
3059      path
3060    );
3061    // example shim on unix
3062    let unix_shim = r#"#!/usr/bin/env -S node
3063"use strict";
3064console.log('Hi!');
3065"#;
3066    let path = PathBuf::from("/node_modules/.bin/example");
3067    assert_eq!(
3068      resolve_execution_path_from_npx_shim(Cow::Borrowed(&path), unix_shim)
3069        .unwrap(),
3070      path
3071    );
3072    // example shim on windows
3073    let windows_shim = r#"#!/bin/sh
3074basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
3075
3076case `uname` in
3077    *CYGWIN*|*MINGW*|*MSYS*) basedir=`cygpath -w "$basedir"`;;
3078esac
3079
3080if [ -x "$basedir/node" ]; then
3081  exec "$basedir/node"  "$basedir/../example/bin/example" "$@"
3082else
3083  exec node  "$basedir/../example/bin/example" "$@"
3084fi"#;
3085    assert_eq!(
3086      resolve_execution_path_from_npx_shim(Cow::Borrowed(&path), windows_shim)
3087        .unwrap(),
3088      path.parent().unwrap().join("../example/bin/example")
3089    );
3090  }
3091}