1use std::borrow::Cow;
4use std::collections::BTreeMap;
5use std::fmt::Debug;
6use std::path::Path;
7use std::path::PathBuf;
8
9use anyhow::Error as AnyError;
10use anyhow::bail;
11use deno_media_type::MediaType;
12use deno_package_json::PackageJson;
13use deno_package_json::PackageJsonRc;
14use deno_path_util::url_to_file_path;
15use deno_semver::Version;
16use deno_semver::VersionReq;
17use lazy_regex::Lazy;
18use regex::Regex;
19use serde_json::Map;
20use serde_json::Value;
21use sys_traits::FileType;
22use sys_traits::FsCanonicalize;
23use sys_traits::FsDirEntry;
24use sys_traits::FsMetadata;
25use sys_traits::FsRead;
26use sys_traits::FsReadDir;
27use url::Url;
28
29use crate::InNpmPackageChecker;
30use crate::IsBuiltInNodeModuleChecker;
31use crate::NpmPackageFolderResolver;
32use crate::PackageJsonResolverRc;
33use crate::PathClean;
34use crate::cache::NodeResolutionSys;
35use crate::errors;
36use crate::errors::DataUrlReferrerError;
37use crate::errors::FinalizeResolutionError;
38use crate::errors::InvalidModuleSpecifierError;
39use crate::errors::InvalidPackageTargetError;
40use crate::errors::LegacyResolveError;
41use crate::errors::MissingPkgJsonError;
42use crate::errors::ModuleNotFoundError;
43use crate::errors::NodeJsErrorCode;
44use crate::errors::NodeJsErrorCoded;
45use crate::errors::NodeResolveError;
46use crate::errors::NodeResolveRelativeJoinError;
47use crate::errors::PackageExportsResolveError;
48use crate::errors::PackageImportNotDefinedError;
49use crate::errors::PackageImportsResolveError;
50use crate::errors::PackageImportsResolveErrorKind;
51use crate::errors::PackagePathNotExportedError;
52use crate::errors::PackageResolveError;
53use crate::errors::PackageSubpathFromDenoModuleResolveError;
54use crate::errors::PackageSubpathResolveError;
55use crate::errors::PackageSubpathResolveErrorKind;
56use crate::errors::PackageTargetNotFoundError;
57use crate::errors::PackageTargetResolveError;
58use crate::errors::PackageTargetResolveErrorKind;
59use crate::errors::ResolvePkgJsonBinExportError;
60use crate::errors::ResolvePkgNpmBinaryCommandsError;
61use crate::errors::TypesNotFoundError;
62use crate::errors::TypesNotFoundErrorData;
63use crate::errors::UnknownBuiltInNodeModuleError;
64use crate::errors::UnsupportedDirImportError;
65use crate::errors::UnsupportedEsmUrlSchemeError;
66use crate::path::UrlOrPath;
67use crate::path::UrlOrPathRef;
68
69pub static IMPORT_CONDITIONS: &[Cow<'static, str>] = &[
70 Cow::Borrowed("deno"),
71 Cow::Borrowed("node"),
72 Cow::Borrowed("import"),
73];
74pub static REQUIRE_CONDITIONS: &[Cow<'static, str>] =
75 &[Cow::Borrowed("require"), Cow::Borrowed("node")];
76static TYPES_ONLY_CONDITIONS: &[Cow<'static, str>] = &[Cow::Borrowed("types")];
77
78#[derive(Debug, Default, Clone)]
79pub struct NodeConditionOptions {
80 pub conditions: Vec<Cow<'static, str>>,
81 pub import_conditions_override: Option<Vec<Cow<'static, str>>>,
85 pub require_conditions_override: Option<Vec<Cow<'static, str>>>,
89}
90
91#[derive(Debug, Clone)]
92struct ConditionResolver {
93 import_conditions: Cow<'static, [Cow<'static, str>]>,
94 require_conditions: Cow<'static, [Cow<'static, str>]>,
95}
96
97impl ConditionResolver {
98 pub fn new(options: NodeConditionOptions) -> Self {
99 fn combine_conditions(
100 user_conditions: Cow<'_, [Cow<'static, str>]>,
101 override_default: Option<Vec<Cow<'static, str>>>,
102 default_conditions: &'static [Cow<'static, str>],
103 ) -> Cow<'static, [Cow<'static, str>]> {
104 let default_conditions = override_default
105 .map(Cow::Owned)
106 .unwrap_or(Cow::Borrowed(default_conditions));
107 if user_conditions.is_empty() {
108 default_conditions
109 } else {
110 let mut new =
111 Vec::with_capacity(user_conditions.len() + default_conditions.len());
112 let mut append =
113 |conditions: Cow<'_, [Cow<'static, str>]>| match conditions {
114 Cow::Borrowed(conditions) => new.extend(conditions.iter().cloned()),
115 Cow::Owned(conditions) => new.extend(conditions),
116 };
117 append(user_conditions);
118 append(default_conditions);
119 Cow::Owned(new)
120 }
121 }
122
123 Self {
124 import_conditions: combine_conditions(
125 Cow::Borrowed(&options.conditions),
126 options.import_conditions_override,
127 IMPORT_CONDITIONS,
128 ),
129 require_conditions: combine_conditions(
130 Cow::Owned(options.conditions),
131 options.require_conditions_override,
132 REQUIRE_CONDITIONS,
133 ),
134 }
135 }
136
137 pub fn resolve(
138 &self,
139 resolution_mode: ResolutionMode,
140 ) -> &[Cow<'static, str>] {
141 match resolution_mode {
142 ResolutionMode::Import => &self.import_conditions,
143 ResolutionMode::Require => &self.require_conditions,
144 }
145 }
146
147 pub fn require_conditions(&self) -> &[Cow<'static, str>] {
148 &self.require_conditions
149 }
150}
151
152#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
153pub enum ResolutionMode {
154 Import,
155 Require,
156}
157
158impl ResolutionMode {
159 pub fn default_conditions(&self) -> &'static [Cow<'static, str>] {
160 match self {
161 ResolutionMode::Import => IMPORT_CONDITIONS,
162 ResolutionMode::Require => REQUIRE_CONDITIONS,
163 }
164 }
165
166 #[cfg(feature = "graph")]
167 pub fn from_deno_graph(mode: deno_graph::source::ResolutionMode) -> Self {
168 use deno_graph::source::ResolutionMode::*;
169 match mode {
170 Import => Self::Import,
171 Require => Self::Require,
172 }
173 }
174}
175
176#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
177pub enum NodeResolutionKind {
178 Execution,
179 Types,
180}
181
182impl NodeResolutionKind {
183 pub fn is_types(&self) -> bool {
184 matches!(self, NodeResolutionKind::Types)
185 }
186
187 #[cfg(feature = "graph")]
188 pub fn from_deno_graph(kind: deno_graph::source::ResolutionKind) -> Self {
189 use deno_graph::source::ResolutionKind::*;
190 match kind {
191 Execution => Self::Execution,
192 Types => Self::Types,
193 }
194 }
195}
196
197#[derive(Debug)]
198pub enum NodeResolution {
199 Module(UrlOrPath),
200 BuiltIn(String),
201}
202
203impl NodeResolution {
204 pub fn into_url(self) -> Result<Url, NodeResolveError> {
205 match self {
206 Self::Module(u) => Ok(u.into_url()?),
207 Self::BuiltIn(specifier) => Ok(if specifier.starts_with("node:") {
208 Url::parse(&specifier).unwrap()
209 } else {
210 Url::parse(&format!("node:{specifier}")).unwrap()
211 }),
212 }
213 }
214}
215
216struct LocalPath {
217 path: PathBuf,
218 known_exists: bool,
219}
220
221enum LocalUrlOrPath {
222 Url(Url),
223 Path(LocalPath),
224}
225
226impl LocalUrlOrPath {
227 pub fn into_url_or_path(self) -> UrlOrPath {
228 match self {
229 LocalUrlOrPath::Url(url) => UrlOrPath::Url(url),
230 LocalUrlOrPath::Path(local_path) => UrlOrPath::Path(local_path.path),
231 }
232 }
233}
234
235struct MaybeTypesResolvedUrl(LocalUrlOrPath);
239
240enum ResolvedMethod {
242 Url,
243 RelativeOrAbsolute,
244 PackageImports,
245 PackageExports,
246 PackageSubPath,
247}
248
249#[derive(Debug, Default, Clone)]
250pub struct NodeResolverOptions {
251 pub conditions: NodeConditionOptions,
252 pub is_browser_platform: bool,
253 pub bundle_mode: bool,
254 pub typescript_version: Option<Version>,
257}
258
259#[derive(Debug)]
260struct ResolutionConfig {
261 pub bundle_mode: bool,
262 pub prefer_browser_field: bool,
263 pub typescript_version: Option<Version>,
264}
265
266#[sys_traits::auto_impl]
267pub trait NodeResolverSys:
268 FsCanonicalize + FsMetadata + FsRead + FsReadDir
269{
270}
271
272#[allow(clippy::disallowed_types)]
273pub type NodeResolverRc<
274 TInNpmPackageChecker,
275 TIsBuiltInNodeModuleChecker,
276 TNpmPackageFolderResolver,
277 TSys,
278> = deno_maybe_sync::MaybeArc<
279 NodeResolver<
280 TInNpmPackageChecker,
281 TIsBuiltInNodeModuleChecker,
282 TNpmPackageFolderResolver,
283 TSys,
284 >,
285>;
286
287#[derive(Debug)]
288pub struct NodeResolver<
289 TInNpmPackageChecker: InNpmPackageChecker,
290 TIsBuiltInNodeModuleChecker: IsBuiltInNodeModuleChecker,
291 TNpmPackageFolderResolver: NpmPackageFolderResolver,
292 TSys: NodeResolverSys,
293> {
294 in_npm_pkg_checker: TInNpmPackageChecker,
295 is_built_in_node_module_checker: TIsBuiltInNodeModuleChecker,
296 npm_pkg_folder_resolver: TNpmPackageFolderResolver,
297 pkg_json_resolver: PackageJsonResolverRc<TSys>,
298 sys: NodeResolutionSys<TSys>,
299 condition_resolver: ConditionResolver,
300 resolution_config: ResolutionConfig,
301}
302
303impl<
304 TInNpmPackageChecker: InNpmPackageChecker,
305 TIsBuiltInNodeModuleChecker: IsBuiltInNodeModuleChecker,
306 TNpmPackageFolderResolver: NpmPackageFolderResolver,
307 TSys: NodeResolverSys,
308>
309 NodeResolver<
310 TInNpmPackageChecker,
311 TIsBuiltInNodeModuleChecker,
312 TNpmPackageFolderResolver,
313 TSys,
314 >
315{
316 pub fn new(
317 in_npm_pkg_checker: TInNpmPackageChecker,
318 is_built_in_node_module_checker: TIsBuiltInNodeModuleChecker,
319 npm_pkg_folder_resolver: TNpmPackageFolderResolver,
320 pkg_json_resolver: PackageJsonResolverRc<TSys>,
321 sys: NodeResolutionSys<TSys>,
322 options: NodeResolverOptions,
323 ) -> Self {
324 Self {
325 in_npm_pkg_checker,
326 is_built_in_node_module_checker,
327 npm_pkg_folder_resolver,
328 pkg_json_resolver,
329 sys,
330 condition_resolver: ConditionResolver::new(NodeConditionOptions {
331 conditions: options.conditions.conditions,
332 import_conditions_override: options
333 .conditions
334 .import_conditions_override
335 .or_else(|| {
336 if options.is_browser_platform {
337 Some(vec![Cow::Borrowed("browser"), Cow::Borrowed("import")])
338 } else {
339 None
340 }
341 }),
342 require_conditions_override: options
343 .conditions
344 .require_conditions_override
345 .or_else(|| {
346 if options.is_browser_platform {
347 Some(vec![Cow::Borrowed("browser"), Cow::Borrowed("require")])
348 } else {
349 None
350 }
351 }),
352 }),
353 resolution_config: ResolutionConfig {
354 bundle_mode: options.bundle_mode,
355 prefer_browser_field: options.is_browser_platform,
356 typescript_version: options.typescript_version,
357 },
358 }
359 }
360
361 pub fn require_conditions(&self) -> &[Cow<'static, str>] {
362 self.condition_resolver.require_conditions()
363 }
364
365 pub fn in_npm_package(&self, specifier: &Url) -> bool {
366 self.in_npm_pkg_checker.in_npm_package(specifier)
367 }
368
369 #[inline(always)]
370 pub fn is_builtin_node_module(&self, specifier: &str) -> bool {
371 self
372 .is_built_in_node_module_checker
373 .is_builtin_node_module(specifier)
374 }
375
376 pub fn resolve(
379 &self,
380 specifier: &str,
381 referrer: &Url,
382 resolution_mode: ResolutionMode,
383 resolution_kind: NodeResolutionKind,
384 ) -> Result<NodeResolution, NodeResolveError> {
385 if self.is_builtin_node_module(specifier) {
389 return Ok(NodeResolution::BuiltIn(specifier.to_string()));
390 }
391
392 if let Ok(url) = Url::parse(specifier) {
393 if url.scheme() == "data" {
394 return Ok(NodeResolution::Module(UrlOrPath::Url(url)));
395 }
396
397 if let Some(module_name) =
398 self.get_module_name_from_builtin_node_module_url(&url)?
399 {
400 return Ok(NodeResolution::BuiltIn(module_name.to_string()));
401 }
402
403 let protocol = url.scheme();
404
405 if protocol != "file" && protocol != "data" {
406 return Err(
407 UnsupportedEsmUrlSchemeError {
408 url_scheme: protocol.to_string(),
409 }
410 .into(),
411 );
412 }
413
414 if referrer.scheme() == "data" {
416 let url = referrer
417 .join(specifier)
418 .map_err(|source| DataUrlReferrerError { source })?;
419 return Ok(NodeResolution::Module(UrlOrPath::Url(url)));
420 }
421 }
422
423 let conditions = self.condition_resolver.resolve(resolution_mode);
424 let referrer = UrlOrPathRef::from_url(referrer);
425 let (url, resolved_kind) = self.module_resolve(
426 specifier,
427 &referrer,
428 resolution_mode,
429 conditions,
430 resolution_kind,
431 )?;
432
433 let url_or_path = self.finalize_resolution(
434 url,
435 resolved_kind,
436 resolution_mode,
437 conditions,
438 resolution_kind,
439 Some(&referrer),
440 )?;
441 let resolve_response = NodeResolution::Module(url_or_path);
442 Ok(resolve_response)
445 }
446
447 fn module_resolve(
448 &self,
449 specifier: &str,
450 referrer: &UrlOrPathRef,
451 resolution_mode: ResolutionMode,
452 conditions: &[Cow<'static, str>],
453 resolution_kind: NodeResolutionKind,
454 ) -> Result<(MaybeTypesResolvedUrl, ResolvedMethod), NodeResolveError> {
455 if should_be_treated_as_relative_or_absolute_path(specifier) {
456 let referrer_url = referrer.url()?;
457 let url = node_join_url(referrer_url, specifier).map_err(|err| {
458 NodeResolveRelativeJoinError {
459 path: specifier.to_string(),
460 base: referrer_url.clone(),
461 source: err,
462 }
463 })?;
464 let url = self.maybe_resolve_types(
465 LocalUrlOrPath::Url(url),
466 Some(referrer),
467 resolution_mode,
468 conditions,
469 resolution_kind,
470 )?;
471 Ok((url, ResolvedMethod::RelativeOrAbsolute))
472 } else if specifier.starts_with('#') {
473 let pkg_config = self
474 .pkg_json_resolver
475 .get_closest_package_json(referrer.path()?)
476 .map_err(PackageImportsResolveErrorKind::PkgJsonLoad)
477 .map_err(|err| PackageImportsResolveError(Box::new(err)))?;
478 Ok((
479 self.package_imports_resolve_internal(
480 specifier,
481 Some(referrer),
482 resolution_mode,
483 pkg_config.as_deref(),
484 conditions,
485 resolution_kind,
486 )?,
487 ResolvedMethod::PackageImports,
488 ))
489 } else if let Ok(url) = Url::parse(specifier) {
490 let url_or_path = self.maybe_resolve_types(
491 LocalUrlOrPath::Url(url),
492 Some(referrer),
493 resolution_mode,
494 conditions,
495 resolution_kind,
496 )?;
497 Ok((url_or_path, ResolvedMethod::Url))
498 } else {
499 Ok(self.package_resolve(
500 specifier,
501 referrer,
502 resolution_mode,
503 conditions,
504 resolution_kind,
505 )?)
506 }
507 }
508
509 fn finalize_resolution(
510 &self,
511 resolved: MaybeTypesResolvedUrl,
512 resolved_method: ResolvedMethod,
513 resolution_mode: ResolutionMode,
514 conditions: &[Cow<'static, str>],
515 resolution_kind: NodeResolutionKind,
516 maybe_referrer: Option<&UrlOrPathRef>,
517 ) -> Result<UrlOrPath, FinalizeResolutionError> {
518 let encoded_sep_re = lazy_regex::regex!(r"%2F|%2C");
519
520 let resolved = resolved.0;
521 let text = match &resolved {
522 LocalUrlOrPath::Url(url) => Cow::Borrowed(url.as_str()),
523 LocalUrlOrPath::Path(LocalPath { path, .. }) => path.to_string_lossy(),
524 };
525 if encoded_sep_re.is_match(&text) {
526 return Err(
527 errors::InvalidModuleSpecifierError {
528 request: text.into_owned(),
529 reason: Cow::Borrowed(
530 "must not include encoded \"/\" or \"\\\\\" characters",
531 ),
532 maybe_referrer: maybe_referrer.map(|r| match r.path() {
533 Ok(path) => path.display().to_string(),
535 Err(_) => r.display().to_string(),
536 }),
537 }
538 .into(),
539 );
540 }
541
542 let (path, maybe_url) = match resolved {
543 LocalUrlOrPath::Url(url) => {
544 if url.scheme() == "file" {
545 (url_to_file_path(&url)?, Some(url))
546 } else {
547 return Ok(UrlOrPath::Url(url));
548 }
549 }
550 LocalUrlOrPath::Path(LocalPath { path, known_exists }) => {
551 if known_exists {
552 return Ok(UrlOrPath::Path(path));
554 } else {
555 (path, None)
556 }
557 }
558 };
559
560 let p_str = path.to_str().unwrap();
566 let path = match p_str.strip_suffix('/') {
567 Some(s) => Cow::Borrowed(Path::new(s)),
568 None => Cow::Owned(path),
569 };
570
571 let maybe_file_type = self.sys.get_file_type(&path);
572 match maybe_file_type {
573 Ok(FileType::Dir) => {
574 if resolution_mode == ResolutionMode::Import
575 && !self.resolution_config.bundle_mode
576 {
577 let suggestion = self.directory_import_suggestion(&path);
578 Err(
579 UnsupportedDirImportError {
580 dir_url: UrlOrPath::Path(path.into_owned()),
581 maybe_referrer: maybe_referrer.map(|r| r.display()),
582 suggestion,
583 }
584 .into(),
585 )
586 } else {
587 let path_with_ext = with_known_extension(&path, "js");
589 if self.sys.is_file(&path_with_ext) {
590 Ok(UrlOrPath::Path(path_with_ext))
591 } else {
592 let (resolved_url, resolved_method) = self
593 .resolve_package_dir_subpath(
594 &path,
595 ".",
596 maybe_referrer,
597 resolution_mode,
598 conditions,
599 resolution_kind,
600 )?;
601 self.finalize_resolution(
602 resolved_url,
603 resolved_method,
604 resolution_mode,
605 conditions,
606 resolution_kind,
607 maybe_referrer,
608 )
609 }
610 }
611 }
612 Ok(FileType::File) => {
613 Ok(
615 maybe_url
616 .map(UrlOrPath::Url)
617 .unwrap_or(UrlOrPath::Path(path.into_owned())),
618 )
619 }
620 _ => {
621 if let Err(e) = maybe_file_type
622 && (resolution_mode == ResolutionMode::Require
623 || self.resolution_config.bundle_mode)
624 && e.kind() == std::io::ErrorKind::NotFound
625 {
626 let file_with_ext = with_known_extension(&path, "js");
627 if self.sys.is_file(&file_with_ext) {
628 return Ok(UrlOrPath::Path(file_with_ext));
629 }
630 }
631
632 Err(
633 ModuleNotFoundError {
634 suggested_ext: self
635 .module_not_found_ext_suggestion(&path, resolved_method),
636 specifier: UrlOrPath::Path(path.into_owned()),
637 maybe_referrer: maybe_referrer.map(|r| r.display()),
638 }
639 .into(),
640 )
641 }
642 }
643 }
644
645 fn module_not_found_ext_suggestion(
646 &self,
647 path: &Path,
648 resolved_method: ResolvedMethod,
649 ) -> Option<&'static str> {
650 fn should_probe(path: &Path, resolved_method: ResolvedMethod) -> bool {
651 if MediaType::from_path(path) != MediaType::Unknown {
652 return false;
653 }
654 match resolved_method {
655 ResolvedMethod::Url
656 | ResolvedMethod::RelativeOrAbsolute
657 | ResolvedMethod::PackageSubPath => true,
658 ResolvedMethod::PackageImports | ResolvedMethod::PackageExports => {
659 false
660 }
661 }
662 }
663
664 if should_probe(path, resolved_method) {
665 ["js", "mjs", "cjs"]
666 .into_iter()
667 .find(|ext| self.sys.is_file(&with_known_extension(path, ext)))
668 } else {
669 None
670 }
671 }
672
673 fn directory_import_suggestion(
674 &self,
675 dir_import_path: &Path,
676 ) -> Option<String> {
677 let dir_index_paths = ["index.mjs", "index.js", "index.cjs"]
678 .into_iter()
679 .map(|file_name| dir_import_path.join(file_name));
680 let file_paths = [
681 with_known_extension(dir_import_path, "js"),
682 with_known_extension(dir_import_path, "mjs"),
683 with_known_extension(dir_import_path, "cjs"),
684 ];
685 dir_index_paths
686 .chain(file_paths)
687 .chain(
688 std::iter::once_with(|| {
689 let package_json_path = dir_import_path.join("package.json");
691 let pkg_json = self
692 .pkg_json_resolver
693 .load_package_json(&package_json_path)
694 .ok()??;
695 let main = pkg_json.main.as_ref()?;
696 Some(dir_import_path.join(main))
697 })
698 .flatten(),
699 )
700 .map(|p| deno_path_util::normalize_path(Cow::Owned(p)))
701 .find(|p| self.sys.is_file(p))
702 .and_then(|suggested_file_path| {
703 let pkg_json = self
704 .pkg_json_resolver
705 .get_closest_package_jsons(&suggested_file_path)
706 .filter_map(|pkg_json| pkg_json.ok())
707 .find(|p| p.name.is_some())?;
708 let pkg_name = pkg_json.name.as_ref()?;
709 let sub_path = suggested_file_path
710 .strip_prefix(pkg_json.dir_path())
711 .ok()?
712 .to_string_lossy()
713 .replace("\\", "/");
714 Some(format!("{}/{}", pkg_name, sub_path))
715 })
716 }
717
718 pub fn resolve_package_subpath_from_deno_module(
719 &self,
720 package_dir: &Path,
721 package_subpath: Option<&str>,
722 maybe_referrer: Option<&Url>,
723 resolution_mode: ResolutionMode,
724 resolution_kind: NodeResolutionKind,
725 ) -> Result<UrlOrPath, PackageSubpathFromDenoModuleResolveError> {
726 let package_subpath = package_subpath
729 .map(|s| Cow::Owned(format!("./{s}")))
730 .unwrap_or_else(|| Cow::Borrowed("."));
731 let maybe_referrer = maybe_referrer.map(UrlOrPathRef::from_url);
732 let conditions = self.condition_resolver.resolve(resolution_mode);
733 let (resolved_url, resolved_method) = self.resolve_package_dir_subpath(
734 package_dir,
735 &package_subpath,
736 maybe_referrer.as_ref(),
737 resolution_mode,
738 conditions,
739 resolution_kind,
740 )?;
741 let url_or_path = self.finalize_resolution(
742 resolved_url,
743 resolved_method,
744 resolution_mode,
745 conditions,
746 resolution_kind,
747 maybe_referrer.as_ref(),
748 )?;
749 Ok(url_or_path)
750 }
751
752 pub fn resolve_binary_export(
753 &self,
754 package_folder: &Path,
755 sub_path: Option<&str>,
756 ) -> Result<PathBuf, ResolvePkgJsonBinExportError> {
757 let (pkg_json, items) = self
758 .resolve_npm_binary_commands_for_package_with_pkg_json(package_folder)?;
759 let path =
760 resolve_bin_entry_value(&pkg_json, &items, sub_path).map_err(|err| {
761 ResolvePkgJsonBinExportError::InvalidBinProperty {
762 message: err.to_string(),
763 }
764 })?;
765 Ok(path.to_path_buf())
766 }
767
768 pub fn resolve_npm_binary_commands_for_package(
769 &self,
770 package_folder: &Path,
771 ) -> Result<BTreeMap<String, PathBuf>, ResolvePkgNpmBinaryCommandsError> {
772 let (_pkg_json, items) = self
773 .resolve_npm_binary_commands_for_package_with_pkg_json(package_folder)?;
774 Ok(items)
775 }
776
777 fn resolve_npm_binary_commands_for_package_with_pkg_json(
778 &self,
779 package_folder: &Path,
780 ) -> Result<
781 (PackageJsonRc, BTreeMap<String, PathBuf>),
782 ResolvePkgNpmBinaryCommandsError,
783 > {
784 let pkg_json_path = package_folder.join("package.json");
785 let Some(package_json) =
786 self.pkg_json_resolver.load_package_json(&pkg_json_path)?
787 else {
788 return Err(ResolvePkgNpmBinaryCommandsError::MissingPkgJson(
789 MissingPkgJsonError { pkg_json_path },
790 ));
791 };
792 let bins = package_json.resolve_bins()?;
793 let items = match bins {
796 deno_package_json::PackageJsonBins::Directory(path_buf) => {
797 self.resolve_npm_commands_from_bin_dir(&path_buf)
798 }
799 deno_package_json::PackageJsonBins::Bins(items) => items,
800 };
801 Ok((package_json, items))
802 }
803
804 pub fn resolve_npm_commands_from_bin_dir(
805 &self,
806 bin_dir: &Path,
807 ) -> BTreeMap<String, PathBuf> {
808 log::debug!("Resolving npm commands in '{}'.", bin_dir.display());
809 let mut result = BTreeMap::new();
810 match self.sys.fs_read_dir(bin_dir) {
811 Ok(entries) => {
812 for entry in entries {
813 let Ok(entry) = entry else {
814 continue;
815 };
816 if let Some((command, path)) =
817 self.resolve_bin_dir_entry_command(entry)
818 {
819 result.insert(command, path);
820 }
821 }
822 }
823 Err(err) => {
824 log::debug!("Failed read_dir for '{}': {:#}", bin_dir.display(), err);
825 }
826 }
827 result
828 }
829
830 fn resolve_bin_dir_entry_command(
831 &self,
832 entry: TSys::ReadDirEntry,
833 ) -> Option<(String, PathBuf)> {
834 if entry.path().extension().is_some() {
835 return None; }
837 let file_type = entry.file_type().ok()?;
838 let path = if file_type.is_file() {
839 entry.path()
840 } else if file_type.is_symlink() {
841 Cow::Owned(self.sys.fs_canonicalize(entry.path()).ok()?)
842 } else {
843 return None;
844 };
845 let text = self.sys.fs_read_to_string_lossy(&path).ok()?;
846 let command_name = entry.file_name().to_string_lossy().into_owned();
847 if let Some(path) = resolve_execution_path_from_npx_shim(path, &text) {
848 log::debug!(
849 "Resolved npx command '{}' to '{}'.",
850 command_name,
851 path.display()
852 );
853 Some((command_name, path))
854 } else {
855 log::debug!("Failed resolving npx command '{}'.", command_name);
856 None
857 }
858 }
859
860 pub fn resolve_package_folder_from_package(
862 &self,
863 specifier: &str,
864 referrer: &UrlOrPathRef,
865 ) -> Result<PathBuf, errors::PackageFolderResolveError> {
866 self
867 .npm_pkg_folder_resolver
868 .resolve_package_folder_from_package(specifier, referrer)
869 }
870
871 fn maybe_resolve_types(
872 &self,
873 url: LocalUrlOrPath,
874 maybe_referrer: Option<&UrlOrPathRef>,
875 resolution_mode: ResolutionMode,
876 conditions: &[Cow<'static, str>],
877 resolution_kind: NodeResolutionKind,
878 ) -> Result<MaybeTypesResolvedUrl, TypesNotFoundError> {
879 if resolution_kind.is_types() {
880 let file_path = match url {
881 LocalUrlOrPath::Url(url) => {
882 match deno_path_util::url_to_file_path(&url) {
883 Ok(path) => LocalPath {
884 path,
885 known_exists: false,
886 },
887 Err(_) => {
888 return Ok(MaybeTypesResolvedUrl(LocalUrlOrPath::Url(url)));
889 }
890 }
891 }
892 LocalUrlOrPath::Path(path) => path,
893 };
894 self.path_to_declaration_path(
895 file_path,
896 maybe_referrer,
897 resolution_mode,
898 conditions,
899 )
900 } else {
901 Ok(MaybeTypesResolvedUrl(url))
902 }
903 }
904
905 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, };
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 ".",
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 if media_type.is_typed() || media_type == MediaType::Css {
1000 return Ok(MaybeTypesResolvedUrl(LocalUrlOrPath::Path(local_path)));
1001 }
1002 Err(TypesNotFoundError(Box::new(TypesNotFoundErrorData {
1003 code_specifier: UrlOrPathRef::from_path(&local_path.path).display(),
1004 maybe_referrer: maybe_referrer.map(|r| r.display()),
1005 })))
1006 }
1007
1008 #[allow(clippy::too_many_arguments)]
1009 pub fn resolve_package_import(
1010 &self,
1011 name: &str,
1012 maybe_referrer: Option<&UrlOrPathRef>,
1013 referrer_pkg_json: Option<&PackageJson>,
1014 resolution_mode: ResolutionMode,
1015 resolution_kind: NodeResolutionKind,
1016 ) -> Result<UrlOrPath, PackageImportsResolveError> {
1017 self
1018 .package_imports_resolve_internal(
1019 name,
1020 maybe_referrer,
1021 resolution_mode,
1022 referrer_pkg_json,
1023 self.condition_resolver.resolve(resolution_mode),
1024 resolution_kind,
1025 )
1026 .map(|url| url.0.into_url_or_path())
1027 }
1028
1029 #[allow(clippy::too_many_arguments)]
1030 fn package_imports_resolve_internal(
1031 &self,
1032 name: &str,
1033 maybe_referrer: Option<&UrlOrPathRef>,
1034 resolution_mode: ResolutionMode,
1035 referrer_pkg_json: Option<&PackageJson>,
1036 conditions: &[Cow<'static, str>],
1037 resolution_kind: NodeResolutionKind,
1038 ) -> Result<MaybeTypesResolvedUrl, PackageImportsResolveError> {
1039 if name == "#" || name.starts_with("#/") || name.ends_with('/') {
1040 let reason = "is not a valid internal imports specifier name";
1041 return Err(
1042 errors::InvalidModuleSpecifierError {
1043 request: name.to_string(),
1044 reason: Cow::Borrowed(reason),
1045 maybe_referrer: maybe_referrer.map(to_specifier_display_string),
1046 }
1047 .into(),
1048 );
1049 }
1050
1051 if let Some(pkg_json) = &referrer_pkg_json
1052 && let Some(resolved_import) = resolve_pkg_json_import(pkg_json, name)
1053 {
1054 let maybe_resolved = self.resolve_package_target(
1055 &pkg_json.path,
1056 resolved_import.target,
1057 resolved_import.sub_path,
1058 resolved_import.package_sub_path,
1059 maybe_referrer,
1060 resolution_mode,
1061 resolved_import.is_pattern,
1062 true,
1063 conditions,
1064 resolution_kind,
1065 )?;
1066 if let Some(resolved) = maybe_resolved {
1067 return Ok(resolved);
1068 }
1069 }
1070
1071 Err(
1072 PackageImportNotDefinedError {
1073 name: name.to_string(),
1074 package_json_path: referrer_pkg_json.map(|p| p.path.clone()),
1075 maybe_referrer: maybe_referrer.map(|r| r.display()),
1076 }
1077 .into(),
1078 )
1079 }
1080
1081 #[allow(clippy::too_many_arguments)]
1082 fn resolve_package_target_string(
1083 &self,
1084 target: &str,
1085 subpath: &str,
1086 match_: &str,
1087 package_json_path: &Path,
1088 maybe_referrer: Option<&UrlOrPathRef>,
1089 resolution_mode: ResolutionMode,
1090 pattern: bool,
1091 internal: bool,
1092 conditions: &[Cow<'static, str>],
1093 resolution_kind: NodeResolutionKind,
1094 ) -> Result<MaybeTypesResolvedUrl, PackageTargetResolveError> {
1095 if !subpath.is_empty() && !pattern && !target.ends_with('/') {
1096 return Err(
1097 InvalidPackageTargetError {
1098 pkg_json_path: package_json_path.to_path_buf(),
1099 sub_path: match_.to_string(),
1100 target: target.to_string(),
1101 is_import: internal,
1102 maybe_referrer: maybe_referrer.map(|r| r.display()),
1103 }
1104 .into(),
1105 );
1106 }
1107 let invalid_segment_re =
1108 lazy_regex::regex!(r"(^|\\|/)(\.\.?|node_modules)(\\|/|$)");
1109 let pattern_re = lazy_regex::regex!(r"\*");
1110 if !target.starts_with("./") {
1111 if internal && !target.starts_with("../") && !target.starts_with('/') {
1112 let target_url = Url::parse(target);
1113 match target_url {
1114 Ok(url) => {
1115 if self
1116 .get_module_name_from_builtin_node_module_url(&url)?
1117 .is_some()
1118 {
1119 return Ok(MaybeTypesResolvedUrl(LocalUrlOrPath::Url(url)));
1120 }
1121 }
1122 Err(_) => {
1123 let export_target = if pattern {
1124 pattern_re.replace(target, |_caps: ®ex::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: ®ex::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 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 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 if package_subpath.ends_with('/') {
1529 }
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 if package_config.name.as_deref() == Some(package_name)
1603 && let Some(exports) = &package_config.exports
1604 {
1605 return self
1606 .package_exports_resolve_internal(
1607 &package_config.path,
1608 &package_subpath,
1609 exports,
1610 Some(referrer),
1611 resolution_mode,
1612 conditions,
1613 resolution_kind,
1614 )
1615 .map(|url| (url, ResolvedMethod::PackageExports))
1616 .map_err(|err| err.into());
1617 }
1618 }
1619
1620 self.resolve_package_subpath_for_package(
1621 package_name,
1622 &package_subpath,
1623 referrer,
1624 resolution_mode,
1625 conditions,
1626 resolution_kind,
1627 )
1628 }
1629
1630 #[allow(clippy::too_many_arguments)]
1631 fn resolve_package_subpath_for_package(
1632 &self,
1633 package_name: &str,
1634 package_subpath: &str,
1635 referrer: &UrlOrPathRef,
1636 resolution_mode: ResolutionMode,
1637 conditions: &[Cow<'static, str>],
1638 resolution_kind: NodeResolutionKind,
1639 ) -> Result<(MaybeTypesResolvedUrl, ResolvedMethod), PackageResolveError> {
1640 let result = self.resolve_package_subpath_for_package_inner(
1641 package_name,
1642 package_subpath,
1643 referrer,
1644 resolution_mode,
1645 conditions,
1646 resolution_kind,
1647 );
1648 if resolution_kind.is_types() && result.is_err() {
1649 let package_name = types_package_name(package_name);
1651 if let Ok(result) = self.resolve_package_subpath_for_package_inner(
1652 &package_name,
1653 package_subpath,
1654 referrer,
1655 resolution_mode,
1656 conditions,
1657 resolution_kind,
1658 ) {
1659 return Ok(result);
1660 }
1661 }
1662 result
1663 }
1664
1665 #[allow(clippy::too_many_arguments)]
1666 fn resolve_package_subpath_for_package_inner(
1667 &self,
1668 package_name: &str,
1669 package_subpath: &str,
1670 referrer: &UrlOrPathRef,
1671 resolution_mode: ResolutionMode,
1672 conditions: &[Cow<'static, str>],
1673 resolution_kind: NodeResolutionKind,
1674 ) -> Result<(MaybeTypesResolvedUrl, ResolvedMethod), PackageResolveError> {
1675 let package_dir_path = self
1676 .npm_pkg_folder_resolver
1677 .resolve_package_folder_from_package(package_name, referrer)?;
1678
1679 self
1694 .resolve_package_dir_subpath(
1695 &package_dir_path,
1696 package_subpath,
1697 Some(referrer),
1698 resolution_mode,
1699 conditions,
1700 resolution_kind,
1701 )
1702 .map_err(|err| err.into())
1703 }
1704
1705 #[allow(clippy::too_many_arguments)]
1706 fn resolve_package_dir_subpath(
1707 &self,
1708 package_dir_path: &Path,
1709 package_subpath: &str,
1710 maybe_referrer: Option<&UrlOrPathRef>,
1711 resolution_mode: ResolutionMode,
1712 conditions: &[Cow<'static, str>],
1713 resolution_kind: NodeResolutionKind,
1714 ) -> Result<(MaybeTypesResolvedUrl, ResolvedMethod), PackageSubpathResolveError>
1715 {
1716 let package_json_path = package_dir_path.join("package.json");
1717 match self
1718 .pkg_json_resolver
1719 .load_package_json(&package_json_path)?
1720 {
1721 Some(pkg_json) => self.resolve_package_subpath(
1722 &pkg_json,
1723 package_subpath,
1724 maybe_referrer,
1725 resolution_mode,
1726 conditions,
1727 resolution_kind,
1728 ),
1729 None => self
1730 .resolve_package_subpath_no_pkg_json(
1731 package_dir_path,
1732 package_subpath,
1733 maybe_referrer,
1734 resolution_mode,
1735 conditions,
1736 resolution_kind,
1737 )
1738 .map(|url| (url, ResolvedMethod::PackageSubPath))
1739 .map_err(|err| {
1740 PackageSubpathResolveErrorKind::LegacyResolve(err).into()
1741 }),
1742 }
1743 }
1744
1745 #[allow(clippy::too_many_arguments)]
1746 fn resolve_package_subpath(
1747 &self,
1748 package_json: &PackageJson,
1749 package_subpath: &str,
1750 referrer: Option<&UrlOrPathRef>,
1751 resolution_mode: ResolutionMode,
1752 conditions: &[Cow<'static, str>],
1753 resolution_kind: NodeResolutionKind,
1754 ) -> Result<(MaybeTypesResolvedUrl, ResolvedMethod), PackageSubpathResolveError>
1755 {
1756 if let Some(exports) = &package_json.exports {
1757 let result = self.package_exports_resolve_internal(
1758 &package_json.path,
1759 package_subpath,
1760 exports,
1761 referrer,
1762 resolution_mode,
1763 conditions,
1764 resolution_kind,
1765 );
1766 match result {
1767 Ok(found) => return Ok((found, ResolvedMethod::PackageExports)),
1768 Err(exports_err) => {
1769 if resolution_kind.is_types() && package_subpath == "." {
1770 return self
1771 .legacy_main_resolve(
1772 package_json,
1773 referrer,
1774 resolution_mode,
1775 conditions,
1776 resolution_kind,
1777 )
1778 .map(|url| (url, ResolvedMethod::PackageSubPath))
1779 .map_err(|err| {
1780 PackageSubpathResolveErrorKind::LegacyResolve(err).into()
1781 });
1782 }
1783 return Err(
1784 PackageSubpathResolveErrorKind::Exports(exports_err).into(),
1785 );
1786 }
1787 }
1788 }
1789
1790 if package_subpath == "." {
1791 self
1792 .legacy_main_resolve(
1793 package_json,
1794 referrer,
1795 resolution_mode,
1796 conditions,
1797 resolution_kind,
1798 )
1799 .map(|url| (url, ResolvedMethod::PackageSubPath))
1800 .map_err(|err| {
1801 PackageSubpathResolveErrorKind::LegacyResolve(err).into_box()
1802 })
1803 } else {
1804 self
1805 .resolve_subpath_exact(
1806 package_json.path.parent().unwrap(),
1807 package_subpath,
1808 Some(package_json),
1809 referrer,
1810 resolution_mode,
1811 conditions,
1812 resolution_kind,
1813 )
1814 .map(|url| (url, ResolvedMethod::PackageSubPath))
1815 .map_err(|err| {
1816 PackageSubpathResolveErrorKind::LegacyResolve(err.into()).into_box()
1817 })
1818 }
1819 }
1820
1821 fn pkg_json_types_versions<'a>(
1822 &'a self,
1823 pkg_json: &'a PackageJson,
1824 resolution_kind: NodeResolutionKind,
1825 ) -> Option<TypesVersions<'a, TSys>> {
1826 if !resolution_kind.is_types() {
1827 return None;
1828 }
1829 pkg_json
1830 .types_versions
1831 .as_ref()
1832 .and_then(|entries| {
1833 let ts_version = self.resolution_config.typescript_version.as_ref()?;
1834 entries
1835 .iter()
1836 .filter_map(|(k, v)| {
1837 let version_req = VersionReq::parse_from_npm(k).ok()?;
1838 version_req.matches(ts_version).then_some(v)
1839 })
1840 .next()
1841 })
1842 .and_then(|value| value.as_object())
1843 .map(|value| TypesVersions {
1844 value,
1845 dir_path: pkg_json.dir_path(),
1846 sys: &self.sys,
1847 })
1848 }
1849
1850 #[allow(clippy::too_many_arguments)]
1851 fn resolve_subpath_exact(
1852 &self,
1853 directory: &Path,
1854 package_subpath: &str,
1855 package_json: Option<&PackageJson>,
1856 referrer: Option<&UrlOrPathRef>,
1857 resolution_mode: ResolutionMode,
1858 conditions: &[Cow<'static, str>],
1859 resolution_kind: NodeResolutionKind,
1860 ) -> Result<MaybeTypesResolvedUrl, TypesNotFoundError> {
1861 assert_ne!(package_subpath, ".");
1862 let types_versions = package_json.and_then(|pkg_json| {
1863 self.pkg_json_types_versions(pkg_json, resolution_kind)
1864 });
1865 let package_subpath = types_versions
1866 .and_then(|v| v.map(package_subpath))
1867 .unwrap_or(Cow::Borrowed(package_subpath));
1868 let file_path = directory.join(package_subpath.as_ref());
1869 self.maybe_resolve_types(
1870 LocalUrlOrPath::Path(LocalPath {
1871 path: file_path,
1872 known_exists: false,
1873 }),
1874 referrer,
1875 resolution_mode,
1876 conditions,
1877 resolution_kind,
1878 )
1879 }
1880
1881 fn resolve_package_subpath_no_pkg_json(
1882 &self,
1883 directory: &Path,
1884 package_subpath: &str,
1885 maybe_referrer: Option<&UrlOrPathRef>,
1886 resolution_mode: ResolutionMode,
1887 conditions: &[Cow<'static, str>],
1888 resolution_kind: NodeResolutionKind,
1889 ) -> Result<MaybeTypesResolvedUrl, LegacyResolveError> {
1890 if package_subpath == "." {
1891 self.legacy_index_resolve(
1892 directory,
1893 maybe_referrer,
1894 resolution_mode,
1895 resolution_kind,
1896 )
1897 } else {
1898 self
1899 .resolve_subpath_exact(
1900 directory,
1901 package_subpath,
1902 None,
1903 maybe_referrer,
1904 resolution_mode,
1905 conditions,
1906 resolution_kind,
1907 )
1908 .map_err(|err| err.into())
1909 }
1910 }
1911
1912 pub(crate) fn legacy_fallback_resolve<'a>(
1913 &self,
1914 package_json: &'a PackageJson,
1915 ) -> Option<&'a str> {
1916 fn filter_empty(value: Option<&str>) -> Option<&str> {
1917 value.map(|v| v.trim()).filter(|v| !v.is_empty())
1918 }
1919 if self.resolution_config.bundle_mode {
1920 let maybe_browser = if self.resolution_config.prefer_browser_field {
1921 filter_empty(package_json.browser.as_deref())
1922 } else {
1923 None
1924 };
1925 maybe_browser
1926 .or(filter_empty(package_json.module.as_deref()))
1927 .or(filter_empty(package_json.main.as_deref()))
1928 } else {
1929 filter_empty(package_json.main.as_deref())
1930 }
1931 }
1932
1933 fn legacy_main_resolve(
1934 &self,
1935 package_json: &PackageJson,
1936 maybe_referrer: Option<&UrlOrPathRef>,
1937 resolution_mode: ResolutionMode,
1938 conditions: &[Cow<'static, str>],
1939 resolution_kind: NodeResolutionKind,
1940 ) -> Result<MaybeTypesResolvedUrl, LegacyResolveError> {
1941 let maybe_main = if resolution_kind.is_types() {
1942 match package_json.types.as_ref() {
1943 Some(types) => {
1944 let types_versions =
1945 self.pkg_json_types_versions(package_json, resolution_kind);
1946 Some(
1947 types_versions
1948 .and_then(|v| v.map(types.as_ref()))
1949 .unwrap_or(Cow::Borrowed(types.as_str())),
1950 )
1951 }
1952 None => {
1953 if let Some(main) = self.legacy_fallback_resolve(package_json) {
1956 let main = package_json.path.parent().unwrap().join(main).clean();
1957 let decl_path_result = self.path_to_declaration_path(
1958 LocalPath {
1959 path: main,
1960 known_exists: false,
1961 },
1962 maybe_referrer,
1963 resolution_mode,
1964 conditions,
1965 );
1966 if let Ok(url_or_path) = decl_path_result {
1968 return Ok(url_or_path);
1969 }
1970 }
1971 None
1972 }
1973 }
1974 } else {
1975 self
1976 .legacy_fallback_resolve(package_json)
1977 .map(Cow::Borrowed)
1978 };
1979
1980 if let Some(main) = maybe_main.as_deref() {
1981 let guess = package_json.path.parent().unwrap().join(main).clean();
1982 if self.sys.is_file(&guess) {
1983 return Ok(self.maybe_resolve_types(
1984 LocalUrlOrPath::Path(LocalPath {
1985 path: guess,
1986 known_exists: true,
1987 }),
1988 maybe_referrer,
1989 resolution_mode,
1990 conditions,
1991 resolution_kind,
1992 )?);
1993 }
1994
1995 let endings = if resolution_kind.is_types() {
1997 match resolution_mode {
1998 ResolutionMode::Require => {
1999 vec![".d.ts", ".d.cts", "/index.d.ts", "/index.d.cts"]
2000 }
2001 ResolutionMode::Import => vec![
2002 ".d.ts",
2003 ".d.mts",
2004 "/index.d.ts",
2005 "/index.d.mts",
2006 ".d.cts",
2007 "/index.d.cts",
2008 ],
2009 }
2010 } else {
2011 vec![".js", "/index.js"]
2012 };
2013 for ending in endings {
2014 let guess = package_json
2015 .path
2016 .parent()
2017 .unwrap()
2018 .join(format!("{main}{ending}"))
2019 .clean();
2020 if self.sys.is_file(&guess) {
2021 return Ok(MaybeTypesResolvedUrl(LocalUrlOrPath::Path(LocalPath {
2023 path: guess,
2024 known_exists: true,
2025 })));
2026 }
2027 }
2028 }
2029
2030 self.legacy_index_resolve(
2031 package_json.path.parent().unwrap(),
2032 maybe_referrer,
2033 resolution_mode,
2034 resolution_kind,
2035 )
2036 }
2037
2038 fn legacy_index_resolve(
2039 &self,
2040 directory: &Path,
2041 maybe_referrer: Option<&UrlOrPathRef>,
2042 resolution_mode: ResolutionMode,
2043 resolution_kind: NodeResolutionKind,
2044 ) -> Result<MaybeTypesResolvedUrl, LegacyResolveError> {
2045 let index_file_names = if resolution_kind.is_types() {
2046 match resolution_mode {
2048 ResolutionMode::Require => vec!["index.d.ts", "index.d.cts"],
2049 ResolutionMode::Import => {
2050 vec!["index.d.ts", "index.d.mts", "index.d.cts"]
2051 }
2052 }
2053 } else {
2054 vec!["index.js"]
2056 };
2057 for index_file_name in index_file_names {
2058 let guess = directory.join(index_file_name).clean();
2059 if self.sys.is_file(&guess) {
2060 return Ok(MaybeTypesResolvedUrl(LocalUrlOrPath::Path(LocalPath {
2062 path: guess,
2063 known_exists: true,
2064 })));
2065 }
2066 }
2067
2068 if resolution_kind.is_types() {
2069 Err(
2070 TypesNotFoundError(Box::new(TypesNotFoundErrorData {
2071 code_specifier: UrlOrPathRef::from_path(&directory.join("index.js"))
2072 .display(),
2073 maybe_referrer: maybe_referrer.map(|r| r.display()),
2074 }))
2075 .into(),
2076 )
2077 } else {
2078 Err(
2079 ModuleNotFoundError {
2080 specifier: UrlOrPath::Path(directory.join("index.js")),
2081 maybe_referrer: maybe_referrer.map(|r| r.display()),
2082 suggested_ext: None,
2083 }
2084 .into(),
2085 )
2086 }
2087 }
2088
2089 pub fn handle_if_in_node_modules(&self, specifier: &Url) -> Option<Url> {
2093 if specifier.scheme() == "file"
2095 && specifier.path().contains("/node_modules/")
2096 {
2097 let specifier = resolve_specifier_into_node_modules(&self.sys, specifier);
2100 return Some(specifier);
2101 }
2102
2103 None
2104 }
2105
2106 fn get_module_name_from_builtin_node_module_url<'url>(
2108 &self,
2109 url: &'url Url,
2110 ) -> Result<Option<&'url str>, UnknownBuiltInNodeModuleError> {
2111 if url.scheme() != "node" {
2112 return Ok(None);
2113 }
2114
2115 let module_name = url.path();
2116
2117 if !self
2118 .is_built_in_node_module_checker
2119 .is_builtin_node_module(module_name)
2120 {
2121 return Err(UnknownBuiltInNodeModuleError {
2122 module_name: module_name.to_string(),
2123 });
2124 }
2125 Ok(Some(module_name))
2126 }
2127}
2128
2129struct ResolvedPkgJsonImport<'a> {
2130 pub target: &'a serde_json::Value,
2131 pub sub_path: &'a str,
2132 pub package_sub_path: &'a str,
2133 pub is_pattern: bool,
2134}
2135
2136fn resolve_pkg_json_import<'a>(
2137 pkg_json: &'a PackageJson,
2138 name: &'a str,
2139) -> Option<ResolvedPkgJsonImport<'a>> {
2140 let imports = pkg_json.imports.as_ref()?;
2141 if let Some((name, target)) =
2142 imports.get_key_value(name).filter(|_| !name.contains('*'))
2143 {
2144 Some(ResolvedPkgJsonImport {
2145 target,
2146 sub_path: "",
2147 package_sub_path: name,
2148 is_pattern: false,
2149 })
2150 } else {
2151 let mut best_match: &'a str = "";
2152 let mut best_match_subpath: &'a str = "";
2153 for key in imports.keys() {
2154 let pattern_index = key.find('*');
2155 if let Some(pattern_index) = pattern_index {
2156 let key_sub = &key[0..pattern_index];
2157 if name.starts_with(key_sub) {
2158 let pattern_trailer = &key[pattern_index + 1..];
2159 if name.len() > key.len()
2160 && name.ends_with(&pattern_trailer)
2161 && pattern_key_compare(best_match, key) == 1
2162 && key.rfind('*') == Some(pattern_index)
2163 {
2164 best_match = key;
2165 best_match_subpath =
2166 &name[pattern_index..(name.len() - pattern_trailer.len())];
2167 }
2168 }
2169 }
2170 }
2171
2172 if !best_match.is_empty() {
2173 let target = imports.get(best_match).unwrap();
2174 Some(ResolvedPkgJsonImport {
2175 target,
2176 sub_path: best_match_subpath,
2177 package_sub_path: best_match,
2178 is_pattern: true,
2179 })
2180 } else {
2181 None
2182 }
2183 }
2184}
2185
2186fn resolve_execution_path_from_npx_shim(
2189 file_path: Cow<Path>,
2190 text: &str,
2191) -> Option<PathBuf> {
2192 static SCRIPT_PATH_RE: Lazy<Regex> =
2193 lazy_regex::lazy_regex!(r#""\$basedir\/([^"]+)" "\$@""#);
2194
2195 let maybe_first_line = {
2196 let index = text.find("\n")?;
2197 Some(&text[0..index])
2198 };
2199
2200 if let Some(first_line) = maybe_first_line {
2201 if first_line == "#!/usr/bin/env node"
2204 || first_line == "#!/usr/bin/env -S node"
2205 {
2206 return Some(file_path.into_owned());
2208 }
2209 }
2210
2211 SCRIPT_PATH_RE
2215 .captures(text)
2216 .and_then(|c| c.get(1))
2217 .map(|relative_path| {
2218 file_path.parent().unwrap().join(relative_path.as_str())
2219 })
2220}
2221
2222fn resolve_bin_entry_value<'a>(
2223 package_json: &PackageJson,
2224 bins: &'a BTreeMap<String, PathBuf>,
2225 bin_name: Option<&str>,
2226) -> Result<&'a Path, AnyError> {
2227 if bins.is_empty() {
2228 bail!(
2229 "'{}' did not have a bin property with a string or non-empty object value",
2230 package_json.path.display()
2231 );
2232 }
2233 let default_bin = package_json.resolve_default_bin_name().ok();
2234 let searching_bin = bin_name.or(default_bin);
2235 match searching_bin.and_then(|bin_name| bins.get(bin_name)) {
2236 Some(e) => Ok(e),
2237 None => {
2238 if bins.len() > 1
2239 && let Some(first) = bins.values().next()
2240 && bins.values().all(|path| path == first)
2241 {
2242 return Ok(first);
2243 }
2244 if bin_name.is_none()
2245 && bins.len() == 1
2246 && let Some(first) = bins.values().next()
2247 {
2248 return Ok(first);
2249 }
2250 let default_bin = package_json.resolve_default_bin_name().ok();
2251 let prefix = package_json
2252 .name
2253 .as_ref()
2254 .map(|n| {
2255 let mut prefix = format!("npm:{}", n);
2256 if let Some(version) = &package_json.version {
2257 prefix.push('@');
2258 prefix.push_str(version);
2259 }
2260 prefix
2261 })
2262 .unwrap_or_default();
2263 let keys = bins
2264 .keys()
2265 .map(|k| {
2266 if prefix.is_empty() {
2267 format!(" * {k}")
2268 } else if Some(k.as_str()) == default_bin {
2269 format!(" * {prefix}")
2270 } else {
2271 format!(" * {prefix}/{k}")
2272 }
2273 })
2274 .collect::<Vec<_>>();
2275 bail!(
2276 "'{}' did not have a bin entry for '{}'{}",
2277 package_json.path.display(),
2278 searching_bin.unwrap_or("<unspecified>"),
2279 if keys.is_empty() {
2280 "".to_string()
2281 } else {
2282 format!("\n\nPossibilities:\n{}", keys.join("\n"))
2283 }
2284 )
2285 }
2286 }
2287}
2288
2289fn should_be_treated_as_relative_or_absolute_path(specifier: &str) -> bool {
2290 if specifier.is_empty() {
2291 return false;
2292 }
2293
2294 if specifier.starts_with('/') {
2295 return true;
2296 }
2297
2298 deno_path_util::is_relative_specifier(specifier)
2299}
2300
2301fn with_known_extension(path: &Path, ext: &str) -> PathBuf {
2304 const NON_DECL_EXTS: &[&str] = &[
2305 "cjs", "js", "json", "jsx", "mjs", "tsx", "d",
2306 ];
2307 const DECL_EXTS: &[&str] = &["cts", "mts", "ts"];
2308
2309 let file_name = match path.file_name() {
2310 Some(value) => value.to_string_lossy(),
2311 None => return path.to_path_buf(),
2312 };
2313 let lowercase_file_name = file_name.to_lowercase();
2314 let period_index = lowercase_file_name.rfind('.').and_then(|period_index| {
2315 let ext = &lowercase_file_name[period_index + 1..];
2316 if DECL_EXTS.contains(&ext) {
2317 if let Some(next_period_index) =
2318 lowercase_file_name[..period_index].rfind('.')
2319 {
2320 if &lowercase_file_name[next_period_index + 1..period_index] == "d" {
2321 Some(next_period_index)
2322 } else {
2323 Some(period_index)
2324 }
2325 } else {
2326 Some(period_index)
2327 }
2328 } else if NON_DECL_EXTS.contains(&ext) {
2329 Some(period_index)
2330 } else {
2331 None
2332 }
2333 });
2334
2335 let file_name = match period_index {
2336 Some(period_index) => &file_name[..period_index],
2337 None => &file_name,
2338 };
2339 path.with_file_name(format!("{file_name}.{ext}"))
2340}
2341
2342fn to_specifier_display_string(url: &UrlOrPathRef) -> String {
2343 if let Ok(path) = url.path() {
2344 path.display().to_string()
2345 } else {
2346 url.display().to_string()
2347 }
2348}
2349
2350fn throw_invalid_subpath(
2351 subpath: String,
2352 package_json_path: &Path,
2353 internal: bool,
2354 maybe_referrer: Option<&UrlOrPathRef>,
2355) -> InvalidModuleSpecifierError {
2356 let ie = if internal { "imports" } else { "exports" };
2357 let reason = format!(
2358 "request is not a valid subpath for the \"{}\" resolution of {}",
2359 ie,
2360 package_json_path.display(),
2361 );
2362 InvalidModuleSpecifierError {
2363 request: subpath,
2364 reason: Cow::Owned(reason),
2365 maybe_referrer: maybe_referrer.map(to_specifier_display_string),
2366 }
2367}
2368
2369pub fn parse_npm_pkg_name<'a>(
2370 specifier: &'a str,
2371 referrer: &UrlOrPathRef,
2372) -> Result<(&'a str, Cow<'static, str>, bool), InvalidModuleSpecifierError> {
2373 let mut separator_index = specifier.find('/');
2374 let mut valid_package_name = true;
2375 let mut is_scoped = false;
2376 if specifier.is_empty() {
2377 valid_package_name = false;
2378 } else if specifier.starts_with('@') {
2379 is_scoped = true;
2380 if let Some(index) = separator_index {
2381 separator_index = specifier[index + 1..]
2382 .find('/')
2383 .map(|new_index| index + 1 + new_index);
2384 } else {
2385 valid_package_name = false;
2386 }
2387 }
2388
2389 let (package_name, subpath) = if let Some(index) = separator_index {
2390 let (package_name, subpath) = specifier.split_at(index);
2391 (package_name, Cow::Owned(format!(".{}", subpath)))
2392 } else {
2393 (specifier, Cow::Borrowed("."))
2394 };
2395
2396 for ch in package_name.chars() {
2398 if ch == '%' || ch == '\\' {
2399 valid_package_name = false;
2400 break;
2401 }
2402 }
2403
2404 if !valid_package_name {
2405 return Err(errors::InvalidModuleSpecifierError {
2406 request: specifier.to_string(),
2407 reason: Cow::Borrowed("is not a valid package name"),
2408 maybe_referrer: Some(to_specifier_display_string(referrer)),
2409 });
2410 }
2411
2412 Ok((package_name, subpath, is_scoped))
2413}
2414
2415pub fn resolve_specifier_into_node_modules(
2422 sys: &impl FsCanonicalize,
2423 specifier: &Url,
2424) -> Url {
2425 deno_path_util::url_to_file_path(specifier)
2426 .ok()
2427 .and_then(|path| {
2430 deno_path_util::fs::canonicalize_path_maybe_not_exists(sys, &path).ok()
2431 })
2432 .and_then(|path| deno_path_util::url_from_file_path(&path).ok())
2433 .unwrap_or_else(|| specifier.clone())
2434}
2435
2436fn pattern_key_compare(a: &str, b: &str) -> i32 {
2437 let a_pattern_index = a.find('*');
2438 let b_pattern_index = b.find('*');
2439
2440 let base_len_a = if let Some(index) = a_pattern_index {
2441 index + 1
2442 } else {
2443 a.len()
2444 };
2445 let base_len_b = if let Some(index) = b_pattern_index {
2446 index + 1
2447 } else {
2448 b.len()
2449 };
2450
2451 if base_len_a > base_len_b {
2452 return -1;
2453 }
2454
2455 if base_len_b > base_len_a {
2456 return 1;
2457 }
2458
2459 if a_pattern_index.is_none() {
2460 return 1;
2461 }
2462
2463 if b_pattern_index.is_none() {
2464 return -1;
2465 }
2466
2467 if a.len() > b.len() {
2468 return -1;
2469 }
2470
2471 if b.len() > a.len() {
2472 return 1;
2473 }
2474
2475 0
2476}
2477
2478pub fn types_package_name(package_name: &str) -> String {
2480 debug_assert!(!package_name.starts_with("@types/"));
2481 format!(
2484 "@types/{}",
2485 package_name.trim_start_matches('@').replace('/', "__")
2486 )
2487}
2488
2489fn node_join_url(url: &Url, path: &str) -> Result<Url, url::ParseError> {
2492 if let Some(suffix) = path.strip_prefix(".//") {
2493 url.join(&format!("./{}", suffix))
2495 } else {
2496 url.join(path)
2497 }
2498}
2499
2500struct TypesVersions<'a, TSys: FsMetadata> {
2501 dir_path: &'a Path,
2502 value: &'a serde_json::Map<std::string::String, serde_json::Value>,
2503 sys: &'a NodeResolutionSys<TSys>,
2504}
2505
2506impl<'a, TSys: FsMetadata> TypesVersions<'a, TSys> {
2507 pub fn map(&self, search: &str) -> Option<Cow<'a, str>> {
2508 let mut search = search
2509 .strip_prefix("./")
2510 .unwrap_or(search)
2511 .trim_matches('/');
2512 for (key, value) in self.value {
2513 let key = key.strip_suffix("./").unwrap_or(key).trim_matches('/');
2514 let is_match = if key == "*" || key == search {
2515 true
2516 } else if let Some(key_prefix) = key.strip_suffix("/*") {
2517 if let Some(new_search) = search.strip_prefix(key_prefix) {
2518 search = new_search.trim_matches('/');
2519 true
2520 } else {
2521 false
2522 }
2523 } else {
2524 false
2525 };
2526 if !is_match {
2527 continue;
2528 }
2529 if let Some(values) = value.as_array() {
2530 for value in values.iter().filter_map(|s| s.as_str()) {
2531 let value = if let Some(asterisk_index) = value.find('*') {
2532 Cow::Owned(format!(
2533 "{}{}{}",
2534 &value[..asterisk_index],
2535 search,
2536 &value[asterisk_index + 1..]
2537 ))
2538 } else {
2539 Cow::Borrowed(value)
2540 };
2541 let path = self.dir_path.join(value.as_ref());
2542 if self.sys.is_file(&path) {
2543 return Some(value);
2544 }
2545 }
2546 }
2547 }
2548 None
2549 }
2550}
2551
2552#[cfg(test)]
2553mod tests {
2554 use deno_package_json::PackageJsonBins;
2555 use serde_json::json;
2556 use sys_traits::FsCreateDirAll;
2557 use sys_traits::FsWrite;
2558 use sys_traits::impls::InMemorySys;
2559
2560 use super::*;
2561
2562 fn build_package_json(json: Value) -> PackageJson {
2563 PackageJson::load_from_value(PathBuf::from("/package.json"), json).unwrap()
2564 }
2565
2566 #[test]
2567 fn test_resolve_bin_entry_value() {
2568 {
2570 let pkg_json = build_package_json(json!({
2571 "name": "pkg",
2572 "version": "1.1.1",
2573 "bin": {
2574 "bin1": "./value1",
2575 "bin2": "./value2",
2576 "pkg": "./value3",
2577 }
2578 }));
2579 let bins = match pkg_json.resolve_bins().unwrap() {
2580 PackageJsonBins::Directory(_) => unreachable!(),
2581 PackageJsonBins::Bins(bins) => bins,
2582 };
2583 assert_eq!(
2584 resolve_bin_entry_value(&pkg_json, &bins, Some("bin1")).unwrap(),
2585 pkg_json.dir_path().join("./value1")
2586 );
2587 assert_eq!(
2588 resolve_bin_entry_value(&pkg_json, &bins, Some("pkg")).unwrap(),
2589 pkg_json.dir_path().join("./value3")
2590 );
2591
2592 assert_eq!(
2594 resolve_bin_entry_value(&pkg_json, &bins, Some("other"))
2595 .err()
2596 .unwrap()
2597 .to_string(),
2598 concat!(
2599 "'/package.json' did not have a bin entry for 'other'\n",
2600 "\n",
2601 "Possibilities:\n",
2602 " * npm:pkg@1.1.1/bin1\n",
2603 " * npm:pkg@1.1.1/bin2\n",
2604 " * npm:pkg@1.1.1"
2605 )
2606 );
2607 }
2608
2609 {
2611 let pkg_json = build_package_json(json!({
2612 "name": "pkg",
2613 "version": "1.1.1",
2614 "bin": {
2615 "bin": "./value1",
2616 "bin2": "./value2",
2617 }
2618 }));
2619 let bins = match pkg_json.resolve_bins().unwrap() {
2620 PackageJsonBins::Directory(_) => unreachable!(),
2621 PackageJsonBins::Bins(bins) => bins,
2622 };
2623 assert_eq!(
2624 resolve_bin_entry_value(&pkg_json, &bins, Some("pkg"))
2625 .err()
2626 .unwrap()
2627 .to_string(),
2628 concat!(
2629 "'/package.json' did not have a bin entry for 'pkg'\n",
2630 "\n",
2631 "Possibilities:\n",
2632 " * npm:pkg@1.1.1/bin\n",
2633 " * npm:pkg@1.1.1/bin2",
2634 )
2635 );
2636 }
2637
2638 {
2640 let pkg_json = build_package_json(json!({
2641 "name": "pkg",
2642 "version": "1.2.3",
2643 "bin": {
2644 "bin1": "./value",
2645 "bin2": "./value",
2646 }
2647 }));
2648 let bins = match pkg_json.resolve_bins().unwrap() {
2649 PackageJsonBins::Directory(_) => unreachable!(),
2650 PackageJsonBins::Bins(bins) => bins,
2651 };
2652 assert_eq!(
2653 resolve_bin_entry_value(&pkg_json, &bins, Some("pkg")).unwrap(),
2654 pkg_json.dir_path().join("./value")
2655 );
2656 }
2657
2658 {
2660 let pkg_json = build_package_json(json!({
2661 "name": "pkg",
2662 "version": "1.2.3",
2663 "bin": {
2664 "something": "./value",
2665 }
2666 }));
2667 let bins = match pkg_json.resolve_bins().unwrap() {
2668 PackageJsonBins::Directory(_) => unreachable!(),
2669 PackageJsonBins::Bins(bins) => bins,
2670 };
2671 assert_eq!(
2672 resolve_bin_entry_value(&pkg_json, &bins, None).unwrap(),
2673 pkg_json.dir_path().join("./value")
2674 );
2675 }
2676
2677 {
2679 let pkg_json = build_package_json(json!({
2680 "name": "pkg",
2681 "version": "1.2.3",
2682 "bin": "./value",
2683 }));
2684 let bins = match pkg_json.resolve_bins().unwrap() {
2685 PackageJsonBins::Directory(_) => unreachable!(),
2686 PackageJsonBins::Bins(bins) => bins,
2687 };
2688 assert_eq!(
2689 resolve_bin_entry_value(&pkg_json, &bins, Some("path"))
2690 .err()
2691 .unwrap()
2692 .to_string(),
2693 concat!(
2694 "'/package.json' did not have a bin entry for 'path'\n",
2695 "\n",
2696 "Possibilities:\n",
2697 " * npm:pkg@1.2.3"
2698 )
2699 );
2700 }
2701
2702 {
2704 let pkg_json = build_package_json(json!({
2705 "name": "pkg",
2706 "bin": {
2707 "bin1": "./value1",
2708 "bin2": "./value2",
2709 }
2710 }));
2711 let bins = match pkg_json.resolve_bins().unwrap() {
2712 PackageJsonBins::Directory(_) => unreachable!(),
2713 PackageJsonBins::Bins(bins) => bins,
2714 };
2715 assert_eq!(
2716 resolve_bin_entry_value(&pkg_json, &bins, Some("pkg"))
2717 .err()
2718 .unwrap()
2719 .to_string(),
2720 concat!(
2721 "'/package.json' did not have a bin entry for 'pkg'\n",
2722 "\n",
2723 "Possibilities:\n",
2724 " * npm:pkg/bin1\n",
2725 " * npm:pkg/bin2",
2726 )
2727 );
2728 }
2729
2730 {
2732 let pkg_json = build_package_json(json!({
2733 "bin": {
2734 "bin1": "./value1",
2735 "bin2": "./value2",
2736 }
2737 }));
2738 let bins = match pkg_json.resolve_bins().unwrap() {
2739 PackageJsonBins::Directory(_) => unreachable!(),
2740 PackageJsonBins::Bins(bins) => bins,
2741 };
2742 assert_eq!(
2743 resolve_bin_entry_value(&pkg_json, &bins, Some("bin"))
2744 .err()
2745 .unwrap()
2746 .to_string(),
2747 concat!(
2748 "'/package.json' did not have a bin entry for 'bin'\n",
2749 "\n",
2750 "Possibilities:\n",
2751 " * bin1\n",
2752 " * bin2",
2753 )
2754 );
2755 }
2756 }
2757
2758 #[test]
2759 fn test_parse_npm_pkg_name() {
2760 let dummy_referrer = Url::parse("http://example.com").unwrap();
2761 let dummy_referrer = UrlOrPathRef::from_url(&dummy_referrer);
2762
2763 assert_eq!(
2764 parse_npm_pkg_name("fetch-blob", &dummy_referrer).unwrap(),
2765 ("fetch-blob", Cow::Borrowed("."), false)
2766 );
2767 assert_eq!(
2768 parse_npm_pkg_name("@vue/plugin-vue", &dummy_referrer).unwrap(),
2769 ("@vue/plugin-vue", Cow::Borrowed("."), true)
2770 );
2771 assert_eq!(
2772 parse_npm_pkg_name("@astrojs/prism/dist/highlighter", &dummy_referrer)
2773 .unwrap(),
2774 (
2775 "@astrojs/prism",
2776 Cow::Owned("./dist/highlighter".to_string()),
2777 true
2778 )
2779 );
2780 }
2781
2782 #[test]
2783 fn test_with_known_extension() {
2784 let cases = &[
2785 ("test", "d.ts", "test.d.ts"),
2786 ("test.d.ts", "ts", "test.ts"),
2787 ("test.worker", "d.ts", "test.worker.d.ts"),
2788 ("test.d.mts", "js", "test.js"),
2789 ];
2790 for (path, ext, expected) in cases {
2791 let actual = with_known_extension(Path::new(path), ext);
2792 assert_eq!(actual.to_string_lossy(), *expected);
2793 }
2794 }
2795
2796 #[test]
2797 fn test_types_package_name() {
2798 assert_eq!(types_package_name("name"), "@types/name");
2799 assert_eq!(
2800 types_package_name("@scoped/package"),
2801 "@types/scoped__package"
2802 );
2803 }
2804
2805 #[test]
2806 fn test_types_versions() {
2807 let dir_path = PathBuf::from("/dir");
2808 let sys = InMemorySys::default();
2809 sys.fs_create_dir_all(dir_path.join("ts3.1")).unwrap();
2810 sys.fs_write(dir_path.join("file.d.ts"), "").unwrap();
2811 sys.fs_write(dir_path.join("ts3.1/file.d.ts"), "").unwrap();
2812 sys.fs_write(dir_path.join("ts3.1/file2.d.ts"), "").unwrap();
2813 let node_resolution_sys = NodeResolutionSys::new(sys, None);
2814
2815 {
2817 let value = serde_json::json!({
2818 "*": ["ts3.1/*"]
2819 });
2820 let types_versions = TypesVersions {
2821 dir_path: &dir_path,
2822 value: value.as_object().unwrap(),
2823 sys: &node_resolution_sys,
2824 };
2825 assert_eq!(types_versions.map("file.d.ts").unwrap(), "ts3.1/file.d.ts");
2826 assert_eq!(
2827 types_versions.map("file2.d.ts").unwrap(),
2828 "ts3.1/file2.d.ts"
2829 );
2830 assert!(types_versions.map("non_existent/file.d.ts").is_none());
2831 }
2832 {
2834 let value = serde_json::json!({
2835 "types.d.ts": ["ts3.1/file.d.ts"]
2836 });
2837 let types_versions = TypesVersions {
2838 dir_path: &dir_path,
2839 value: value.as_object().unwrap(),
2840 sys: &node_resolution_sys,
2841 };
2842 assert_eq!(types_versions.map("types.d.ts").unwrap(), "ts3.1/file.d.ts");
2843 assert!(types_versions.map("file2.d.ts").is_none());
2844 }
2845 {
2847 let value = serde_json::json!({
2848 "types.d.ts": ["ts3.1/file.d.ts"],
2849 "other.d.ts": ["ts3.1/file2.d.ts"],
2850 });
2851 let types_versions = TypesVersions {
2852 dir_path: &dir_path,
2853 value: value.as_object().unwrap(),
2854 sys: &node_resolution_sys,
2855 };
2856 assert_eq!(types_versions.map("types.d.ts").unwrap(), "ts3.1/file.d.ts");
2857 assert_eq!(
2858 types_versions.map("other.d.ts").unwrap(),
2859 "ts3.1/file2.d.ts"
2860 );
2861 assert!(types_versions.map("file2.d.ts").is_none());
2862 }
2863 {
2865 let value = serde_json::json!({
2866 "*": ["ts3.1/*", "ts3.1/file2.d.ts"]
2867 });
2868 let types_versions = TypesVersions {
2869 dir_path: &dir_path,
2870 value: value.as_object().unwrap(),
2871 sys: &node_resolution_sys,
2872 };
2873 assert_eq!(
2874 types_versions.map("testing/types.d.ts").unwrap(),
2875 "ts3.1/file2.d.ts"
2876 );
2877 }
2878 {
2880 let value = serde_json::json!({
2881 "sub/*": ["ts3.1/file.d.ts"]
2882 });
2883 let types_versions = TypesVersions {
2884 dir_path: &dir_path,
2885 value: value.as_object().unwrap(),
2886 sys: &node_resolution_sys,
2887 };
2888 assert_eq!(
2889 types_versions.map("sub/types.d.ts").unwrap(),
2890 "ts3.1/file.d.ts"
2891 );
2892 }
2893 {
2895 let value = serde_json::json!({
2896 "sub/*": ["ts3.1/*"]
2897 });
2898 let types_versions = TypesVersions {
2899 dir_path: &dir_path,
2900 value: value.as_object().unwrap(),
2901 sys: &node_resolution_sys,
2902 };
2903 assert_eq!(
2904 types_versions.map("sub/file.d.ts").unwrap(),
2905 "ts3.1/file.d.ts"
2906 );
2907 }
2908 }
2909
2910 #[test]
2911 fn test_resolve_execution_path_from_npx_shim() {
2912 let unix_shim = r#"#!/usr/bin/env node
2914"use strict";
2915console.log('Hi!');
2916"#;
2917 let path = PathBuf::from("/node_modules/.bin/example");
2918 assert_eq!(
2919 resolve_execution_path_from_npx_shim(Cow::Borrowed(&path), unix_shim)
2920 .unwrap(),
2921 path
2922 );
2923 let unix_shim = r#"#!/usr/bin/env -S node
2925"use strict";
2926console.log('Hi!');
2927"#;
2928 let path = PathBuf::from("/node_modules/.bin/example");
2929 assert_eq!(
2930 resolve_execution_path_from_npx_shim(Cow::Borrowed(&path), unix_shim)
2931 .unwrap(),
2932 path
2933 );
2934 let windows_shim = r#"#!/bin/sh
2936basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
2937
2938case `uname` in
2939 *CYGWIN*|*MINGW*|*MSYS*) basedir=`cygpath -w "$basedir"`;;
2940esac
2941
2942if [ -x "$basedir/node" ]; then
2943 exec "$basedir/node" "$basedir/../example/bin/example" "$@"
2944else
2945 exec node "$basedir/../example/bin/example" "$@"
2946fi"#;
2947 assert_eq!(
2948 resolve_execution_path_from_npx_shim(Cow::Borrowed(&path), windows_shim)
2949 .unwrap(),
2950 path.parent().unwrap().join("../example/bin/example")
2951 );
2952 }
2953}