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