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