From aa286fdecb15461ef8ddd4c372f5a13e01e1cb7b Mon Sep 17 00:00:00 2001 From: David Sherret Date: Mon, 24 Apr 2023 19:44:35 -0400 Subject: [PATCH] refactor(ext/node): allow injecting `NodeFs` from CLI (#18829) This allows providing a `NodeFs` as part of the `WorkerOptions`. --- cli/build.rs | 6 +- cli/lsp/documents.rs | 13 +- cli/lsp/language_server.rs | 10 +- cli/module_loader.rs | 13 +- cli/node.rs | 7 +- cli/npm/resolvers/local.rs | 11 +- cli/npm/resolvers/mod.rs | 3 + cli/proc_state.rs | 14 +- cli/standalone/mod.rs | 2 + cli/tools/check.rs | 6 +- cli/tools/task.rs | 8 +- cli/tsc/mod.rs | 19 +- cli/worker.rs | 21 +- ext/node/analyze.rs | 110 +- ext/node/lib.rs | 112 +- ext/node/ops/require.rs | 138 +- ext/node/package_json.rs | 10 +- ext/node/resolution.rs | 2113 +++++++++++++++++++---------- ext/node/resolver.rs | 686 ---------- runtime/build.rs | 3 +- runtime/examples/hello_runtime.rs | 1 + runtime/lib.rs | 1 - runtime/web_worker.rs | 5 +- runtime/worker.rs | 6 +- 24 files changed, 1631 insertions(+), 1687 deletions(-) delete mode 100644 ext/node/resolver.rs diff --git a/cli/build.rs b/cli/build.rs index 9be441bcc4..7a3252e20b 100644 --- a/cli/build.rs +++ b/cli/build.rs @@ -2,6 +2,7 @@ use std::env; use std::path::PathBuf; +use std::sync::Arc; use deno_core::snapshot_util::*; use deno_core::Extension; @@ -361,7 +362,10 @@ fn create_cli_snapshot(snapshot_path: PathBuf) { deno_http::deno_http::init_ops(), deno_io::deno_io::init_ops(Default::default()), deno_fs::deno_fs::init_ops::<_, PermissionsContainer>(false, StdFs), - deno_node::deno_node::init_ops::(None), + deno_node::deno_node::init_ops::( + None, + Some(Arc::new(deno_node::RealFs)), + ), cli::init_ops_and_esm(), // NOTE: This needs to be init_ops_and_esm! ]; diff --git a/cli/lsp/documents.rs b/cli/lsp/documents.rs index fd40bb95f2..31aa3ae8ef 100644 --- a/cli/lsp/documents.rs +++ b/cli/lsp/documents.rs @@ -17,7 +17,6 @@ use crate::file_fetcher::get_source_from_bytes; use crate::file_fetcher::map_content_type; use crate::file_fetcher::SUPPORTED_SCHEMES; use crate::lsp::logging::lsp_warn; -use crate::node::CliNodeResolver; use crate::npm::CliNpmRegistryApi; use crate::npm::NpmResolution; use crate::npm::PackageJsonDepsInstaller; @@ -39,8 +38,8 @@ use deno_graph::Resolution; use deno_runtime::deno_node; use deno_runtime::deno_node::NodeResolution; use deno_runtime::deno_node::NodeResolutionMode; +use deno_runtime::deno_node::NodeResolver; use deno_runtime::deno_node::PackageJson; -use deno_runtime::deno_node::RealFs; use deno_runtime::permissions::PermissionsContainer; use deno_semver::npm::NpmPackageReq; use deno_semver::npm::NpmPackageReqReference; @@ -1057,7 +1056,7 @@ impl Documents { &self, specifiers: Vec, referrer_doc: &AssetOrDocument, - maybe_node_resolver: Option<&Arc>, + maybe_node_resolver: Option<&Arc>, ) -> Vec> { let referrer = referrer_doc.specifier(); let dependencies = match referrer_doc { @@ -1071,7 +1070,7 @@ impl Documents { // we're in an npm package, so use node resolution results.push(Some(NodeResolution::into_specifier_and_media_type( node_resolver - .resolve::( + .resolve( &specifier, referrer, NodeResolutionMode::Types, @@ -1419,7 +1418,7 @@ impl Documents { fn resolve_dependency( &self, specifier: &ModuleSpecifier, - maybe_node_resolver: Option<&Arc>, + maybe_node_resolver: Option<&Arc>, ) -> Option<(ModuleSpecifier, MediaType)> { if let Ok(npm_ref) = NpmPackageReqReference::from_specifier(specifier) { return node_resolve_npm_req_ref(npm_ref, maybe_node_resolver); @@ -1454,12 +1453,12 @@ impl Documents { fn node_resolve_npm_req_ref( npm_req_ref: NpmPackageReqReference, - maybe_node_resolver: Option<&Arc>, + maybe_node_resolver: Option<&Arc>, ) -> Option<(ModuleSpecifier, MediaType)> { maybe_node_resolver.map(|node_resolver| { NodeResolution::into_specifier_and_media_type( node_resolver - .resolve_npm_req_reference::( + .resolve_npm_req_reference( &npm_req_ref, NodeResolutionMode::Types, &mut PermissionsContainer::allow_all(), diff --git a/cli/lsp/language_server.rs b/cli/lsp/language_server.rs index f1b9cb4347..e7968a6655 100644 --- a/cli/lsp/language_server.rs +++ b/cli/lsp/language_server.rs @@ -9,6 +9,7 @@ use deno_core::serde_json; use deno_core::serde_json::json; use deno_core::serde_json::Value; use deno_core::ModuleSpecifier; +use deno_runtime::deno_node; use deno_runtime::deno_node::NodeResolver; use deno_runtime::deno_node::PackageJson; use deno_runtime::deno_web::BlobStore; @@ -79,7 +80,6 @@ use crate::file_fetcher::FileFetcher; use crate::graph_util; use crate::http_util::HttpClient; use crate::lsp::urls::LspUrlKind; -use crate::node::CliNodeResolver; use crate::npm::create_npm_fs_resolver; use crate::npm::CliNpmRegistryApi; use crate::npm::CliNpmResolver; @@ -103,7 +103,7 @@ pub struct StateSnapshot { pub cache_metadata: cache::CacheMetadata, pub documents: Documents, pub maybe_import_map: Option>, - pub maybe_node_resolver: Option>, + pub maybe_node_resolver: Option>, pub maybe_npm_resolver: Option>, } @@ -449,6 +449,7 @@ fn create_lsp_structs( let resolution = Arc::new(NpmResolution::from_serialized(api.clone(), None, None)); let fs_resolver = create_npm_fs_resolver( + Arc::new(deno_node::RealFs), npm_cache.clone(), &progress_bar, registry_url.clone(), @@ -700,9 +701,11 @@ impl Inner { self.npm_resolution.snapshot(), None, )); + let node_fs = Arc::new(deno_node::RealFs); let npm_resolver = Arc::new(CliNpmResolver::new( npm_resolution.clone(), create_npm_fs_resolver( + node_fs.clone(), self.npm_cache.clone(), &ProgressBar::new(ProgressBarStyle::TextOnly), self.npm_api.base_url().clone(), @@ -711,7 +714,8 @@ impl Inner { ), None, )); - let node_resolver = Arc::new(NodeResolver::new(npm_resolver.clone())); + let node_resolver = + Arc::new(NodeResolver::new(node_fs, npm_resolver.clone())); Arc::new(StateSnapshot { assets: self.assets.snapshot(), cache_metadata: self.cache_metadata.clone(), diff --git a/cli/module_loader.rs b/cli/module_loader.rs index c4ef0ed7e9..5a7743ef26 100644 --- a/cli/module_loader.rs +++ b/cli/module_loader.rs @@ -12,7 +12,6 @@ use crate::graph_util::ModuleGraphBuilder; use crate::graph_util::ModuleGraphContainer; use crate::node; use crate::node::CliNodeCodeTranslator; -use crate::node::CliNodeResolver; use crate::proc_state::CjsResolutionStore; use crate::proc_state::FileWatcherReporter; use crate::proc_state::ProcState; @@ -51,7 +50,7 @@ use deno_lockfile::Lockfile; use deno_runtime::deno_node; use deno_runtime::deno_node::NodeResolution; use deno_runtime::deno_node::NodeResolutionMode; -use deno_runtime::deno_node::RealFs; +use deno_runtime::deno_node::NodeResolver; use deno_runtime::permissions::PermissionsContainer; use deno_semver::npm::NpmPackageReqReference; use std::borrow::Cow; @@ -244,7 +243,7 @@ pub struct CliModuleLoader { graph_container: Arc, module_load_preparer: Arc, node_code_translator: Arc, - node_resolver: Arc, + node_resolver: Arc, parsed_source_cache: Arc, resolver: Arc, } @@ -387,7 +386,7 @@ impl CliModuleLoader { self.root_permissions.clone() }; // translate cjs to esm if it's cjs and inject node globals - self.node_code_translator.translate_cjs_to_esm::( + self.node_code_translator.translate_cjs_to_esm( specifier, &code, &mut permissions, @@ -466,7 +465,7 @@ impl ModuleLoader for CliModuleLoader { if self.node_resolver.in_npm_package(referrer) { // we're in an npm package, so use node resolution return self - .handle_node_resolve_result(self.node_resolver.resolve::( + .handle_node_resolve_result(self.node_resolver.resolve( specifier, referrer, NodeResolutionMode::Execution, @@ -492,7 +491,7 @@ impl ModuleLoader for CliModuleLoader { return match graph.get(specifier) { Some(Module::Npm(module)) => self .handle_node_resolve_result( - self.node_resolver.resolve_npm_reference::( + self.node_resolver.resolve_npm_reference( &module.nv_reference, NodeResolutionMode::Execution, &mut permissions, @@ -554,7 +553,7 @@ impl ModuleLoader for CliModuleLoader { { return self .handle_node_resolve_result( - self.node_resolver.resolve_npm_req_reference::( + self.node_resolver.resolve_npm_req_reference( &reference, NodeResolutionMode::Execution, &mut permissions, diff --git a/cli/node.rs b/cli/node.rs index 3ec9500e88..8b54d0d422 100644 --- a/cli/node.rs +++ b/cli/node.rs @@ -1,7 +1,6 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. use std::collections::HashSet; -use std::sync::Arc; use deno_ast::swc::common::SyntaxContext; use deno_ast::view::Node; @@ -15,15 +14,11 @@ use deno_core::error::AnyError; use deno_runtime::deno_node::analyze::CjsAnalysis as ExtNodeCjsAnalysis; use deno_runtime::deno_node::analyze::CjsEsmCodeAnalyzer; use deno_runtime::deno_node::analyze::NodeCodeTranslator; -use deno_runtime::deno_node::NodeResolver; use crate::cache::NodeAnalysisCache; -use crate::npm::CliNpmResolver; use crate::util::fs::canonicalize_path_maybe_not_exists; -pub type CliNodeCodeTranslator = - NodeCodeTranslator>; -pub type CliNodeResolver = NodeResolver>; +pub type CliNodeCodeTranslator = NodeCodeTranslator; /// Resolves a specifier that is pointing into a node_modules folder. /// diff --git a/cli/npm/resolvers/local.rs b/cli/npm/resolvers/local.rs index a44afc9359..e2919f6ee8 100644 --- a/cli/npm/resolvers/local.rs +++ b/cli/npm/resolvers/local.rs @@ -23,6 +23,7 @@ use deno_npm::resolution::NpmResolutionSnapshot; use deno_npm::NpmPackageCacheFolderId; use deno_npm::NpmPackageId; use deno_runtime::deno_core::futures; +use deno_runtime::deno_node::NodeFs; use deno_runtime::deno_node::NodePermissions; use deno_runtime::deno_node::NodeResolutionMode; use deno_runtime::deno_node::PackageJson; @@ -43,6 +44,7 @@ use super::common::NpmPackageFsResolver; /// and resolves packages from it. #[derive(Debug)] pub struct LocalNpmPackageResolver { + fs: Arc, cache: Arc, progress_bar: ProgressBar, resolution: Arc, @@ -53,6 +55,7 @@ pub struct LocalNpmPackageResolver { impl LocalNpmPackageResolver { pub fn new( + fs: Arc, cache: Arc, progress_bar: ProgressBar, registry_url: Url, @@ -60,6 +63,7 @@ impl LocalNpmPackageResolver { resolution: Arc, ) -> Self { Self { + fs, cache, progress_bar, resolution, @@ -149,9 +153,10 @@ impl NpmPackageFsResolver for LocalNpmPackageResolver { if sub_dir.is_dir() { // if doing types resolution, only resolve the package if it specifies a types property if mode.is_types() && !name.starts_with("@types/") { - let package_json = PackageJson::load_skip_read_permission::< - deno_runtime::deno_node::RealFs, - >(sub_dir.join("package.json"))?; + let package_json = PackageJson::load_skip_read_permission( + &*self.fs, + sub_dir.join("package.json"), + )?; if package_json.types.is_some() { return Ok(sub_dir); } diff --git a/cli/npm/resolvers/mod.rs b/cli/npm/resolvers/mod.rs index 8b871beaf7..fa83cdf59c 100644 --- a/cli/npm/resolvers/mod.rs +++ b/cli/npm/resolvers/mod.rs @@ -18,6 +18,7 @@ use deno_npm::resolution::NpmResolutionSnapshot; use deno_npm::resolution::PackageReqNotFoundError; use deno_npm::resolution::SerializedNpmResolutionSnapshot; use deno_npm::NpmPackageId; +use deno_runtime::deno_node; use deno_runtime::deno_node::NodePermissions; use deno_runtime::deno_node::NodeResolutionMode; use deno_runtime::deno_node::NpmResolver; @@ -269,6 +270,7 @@ impl NpmResolver for CliNpmResolver { } pub fn create_npm_fs_resolver( + fs: Arc, cache: Arc, progress_bar: &ProgressBar, registry_url: Url, @@ -277,6 +279,7 @@ pub fn create_npm_fs_resolver( ) -> Arc { match maybe_node_modules_path { Some(node_modules_folder) => Arc::new(LocalNpmPackageResolver::new( + fs, cache, progress_bar.clone(), registry_url, diff --git a/cli/proc_state.rs b/cli/proc_state.rs index 6b7e9b1f28..b6529d3a07 100644 --- a/cli/proc_state.rs +++ b/cli/proc_state.rs @@ -19,7 +19,6 @@ use crate::http_util::HttpClient; use crate::module_loader::ModuleLoadPreparer; use crate::node::CliCjsEsmCodeAnalyzer; use crate::node::CliNodeCodeTranslator; -use crate::node::CliNodeResolver; use crate::npm::create_npm_fs_resolver; use crate::npm::CliNpmRegistryApi; use crate::npm::CliNpmResolver; @@ -38,6 +37,7 @@ use deno_core::ModuleSpecifier; use deno_core::SharedArrayBufferStore; use deno_runtime::deno_broadcast_channel::InMemoryBroadcastChannel; +use deno_runtime::deno_node; use deno_runtime::deno_node::analyze::NodeCodeTranslator; use deno_runtime::deno_node::NodeResolver; use deno_runtime::deno_tls::rustls::RootCertStore; @@ -79,7 +79,8 @@ pub struct Inner { pub module_graph_builder: Arc, pub module_load_preparer: Arc, pub node_code_translator: Arc, - pub node_resolver: Arc, + pub node_fs: Arc, + pub node_resolver: Arc, pub npm_api: Arc, pub npm_cache: Arc, pub npm_resolver: Arc, @@ -150,6 +151,7 @@ impl ProcState { module_graph_builder: self.module_graph_builder.clone(), module_load_preparer: self.module_load_preparer.clone(), node_code_translator: self.node_code_translator.clone(), + node_fs: self.node_fs.clone(), node_resolver: self.node_resolver.clone(), npm_api: self.npm_api.clone(), npm_cache: self.npm_cache.clone(), @@ -245,7 +247,9 @@ impl ProcState { npm_snapshot, lockfile.as_ref().cloned(), )); + let node_fs = Arc::new(deno_node::RealFs); let npm_fs_resolver = create_npm_fs_resolver( + node_fs.clone(), npm_cache, &progress_bar, npm_registry_url, @@ -308,11 +312,14 @@ impl ProcState { let node_analysis_cache = NodeAnalysisCache::new(caches.node_analysis_db(&dir)); let cjs_esm_analyzer = CliCjsEsmCodeAnalyzer::new(node_analysis_cache); + let node_resolver = + Arc::new(NodeResolver::new(node_fs.clone(), npm_resolver.clone())); let node_code_translator = Arc::new(NodeCodeTranslator::new( cjs_esm_analyzer, + node_fs.clone(), + node_resolver.clone(), npm_resolver.clone(), )); - let node_resolver = Arc::new(NodeResolver::new(npm_resolver.clone())); let type_checker = Arc::new(TypeChecker::new( dir.clone(), caches.clone(), @@ -365,6 +372,7 @@ impl ProcState { maybe_file_watcher_reporter, module_graph_builder, node_code_translator, + node_fs, node_resolver, npm_api, npm_cache, diff --git a/cli/standalone/mod.rs b/cli/standalone/mod.rs index a2872e9b92..669ad1d813 100644 --- a/cli/standalone/mod.rs +++ b/cli/standalone/mod.rs @@ -190,6 +190,7 @@ fn create_web_worker_callback( root_cert_store: Some(ps.root_cert_store.clone()), seed: ps.options.seed(), module_loader, + node_fs: Some(ps.node_fs.clone()), npm_resolver: None, // not currently supported create_web_worker_cb, preload_module_cb: web_worker_cb.clone(), @@ -285,6 +286,7 @@ pub async fn run( should_break_on_first_statement: false, should_wait_for_inspector_session: false, module_loader, + node_fs: Some(ps.node_fs.clone()), npm_resolver: None, // not currently supported get_error_class_fn: Some(&get_error_class_name), cache_storage_dir: None, diff --git a/cli/tools/check.rs b/cli/tools/check.rs index 36bc25d6a8..4fb6800fa0 100644 --- a/cli/tools/check.rs +++ b/cli/tools/check.rs @@ -9,6 +9,7 @@ use deno_core::error::AnyError; use deno_graph::Module; use deno_graph::ModuleGraph; use deno_runtime::colors; +use deno_runtime::deno_node::NodeResolver; use once_cell::sync::Lazy; use regex::Regex; @@ -21,7 +22,6 @@ use crate::cache::Caches; use crate::cache::DenoDir; use crate::cache::FastInsecureHasher; use crate::cache::TypeCheckCache; -use crate::node::CliNodeResolver; use crate::npm::CliNpmResolver; use crate::tsc; use crate::version; @@ -42,7 +42,7 @@ pub struct TypeChecker { deno_dir: DenoDir, caches: Arc, cli_options: Arc, - node_resolver: Arc, + node_resolver: Arc, npm_resolver: Arc, } @@ -51,7 +51,7 @@ impl TypeChecker { deno_dir: DenoDir, caches: Arc, cli_options: Arc, - node_resolver: Arc, + node_resolver: Arc, npm_resolver: Arc, ) -> Self { Self { diff --git a/cli/tools/task.rs b/cli/tools/task.rs index 898cdd8d90..5d34d39c75 100644 --- a/cli/tools/task.rs +++ b/cli/tools/task.rs @@ -4,7 +4,6 @@ use crate::args::CliOptions; use crate::args::Flags; use crate::args::TaskFlags; use crate::colors; -use crate::node::CliNodeResolver; use crate::npm::CliNpmResolver; use crate::proc_state::ProcState; use crate::util::fs::canonicalize_path; @@ -13,7 +12,7 @@ use deno_core::anyhow::Context; use deno_core::error::AnyError; use deno_core::futures; use deno_core::futures::future::LocalBoxFuture; -use deno_runtime::deno_node::RealFs; +use deno_runtime::deno_node::NodeResolver; use deno_semver::npm::NpmPackageNv; use deno_task_shell::ExecuteResult; use deno_task_shell::ShellCommand; @@ -236,13 +235,12 @@ impl ShellCommand for NpmPackageBinCommand { fn resolve_npm_commands( npm_resolver: &CliNpmResolver, - node_resolver: &CliNodeResolver, + node_resolver: &NodeResolver, ) -> Result>, AnyError> { let mut result = HashMap::new(); let snapshot = npm_resolver.snapshot(); for id in snapshot.top_level_packages() { - let bin_commands = - node_resolver.resolve_binary_commands::(&id.nv)?; + let bin_commands = node_resolver.resolve_binary_commands(&id.nv)?; for bin_command in bin_commands { result.insert( bin_command.to_string(), diff --git a/cli/tsc/mod.rs b/cli/tsc/mod.rs index 0d956b661f..aa589a1ca9 100644 --- a/cli/tsc/mod.rs +++ b/cli/tsc/mod.rs @@ -4,7 +4,6 @@ use crate::args::TsConfig; use crate::args::TypeCheckMode; use crate::cache::FastInsecureHasher; use crate::node; -use crate::node::CliNodeResolver; use crate::util::checksum; use crate::util::path::mapped_specifier_for_tsc; @@ -35,7 +34,7 @@ use deno_graph::ResolutionResolved; use deno_runtime::deno_node; use deno_runtime::deno_node::NodeResolution; use deno_runtime::deno_node::NodeResolutionMode; -use deno_runtime::deno_node::RealFs; +use deno_runtime::deno_node::NodeResolver; use deno_runtime::permissions::PermissionsContainer; use deno_semver::npm::NpmPackageReqReference; use lsp_types::Url; @@ -307,7 +306,7 @@ pub struct Request { pub debug: bool, pub graph: Arc, pub hash_data: u64, - pub maybe_node_resolver: Option>, + pub maybe_node_resolver: Option>, pub maybe_tsbuildinfo: Option, /// A vector of strings that represent the root/entry point modules for the /// program. @@ -331,7 +330,7 @@ struct State { graph: Arc, maybe_tsbuildinfo: Option, maybe_response: Option, - maybe_node_resolver: Option>, + maybe_node_resolver: Option>, remapped_specifiers: HashMap, root_map: HashMap, current_dir: PathBuf, @@ -341,7 +340,7 @@ impl State { pub fn new( graph: Arc, hash_data: u64, - maybe_node_resolver: Option>, + maybe_node_resolver: Option>, maybe_tsbuildinfo: Option, root_map: HashMap, remapped_specifiers: HashMap, @@ -637,7 +636,7 @@ fn resolve_graph_specifier_types( } Some(Module::Npm(module)) => { if let Some(node_resolver) = &state.maybe_node_resolver { - let maybe_resolution = node_resolver.resolve_npm_reference::( + let maybe_resolution = node_resolver.resolve_npm_reference( &module.nv_reference, NodeResolutionMode::Types, &mut PermissionsContainer::allow_all(), @@ -655,9 +654,7 @@ fn resolve_graph_specifier_types( let specifier = node::resolve_specifier_into_node_modules(&module.specifier); NodeResolution::into_specifier_and_media_type( - node_resolver - .url_to_node_resolution::(specifier) - .ok(), + node_resolver.url_to_node_resolution(specifier).ok(), ) })) } @@ -678,7 +675,7 @@ fn resolve_non_graph_specifier_types( // we're in an npm package, so use node resolution Ok(Some(NodeResolution::into_specifier_and_media_type( node_resolver - .resolve::( + .resolve( specifier, referrer, NodeResolutionMode::Types, @@ -692,7 +689,7 @@ fn resolve_non_graph_specifier_types( // we don't need this special code here. // This could occur when resolving npm:@types/node when it is // injected and not part of the graph - let maybe_resolution = node_resolver.resolve_npm_req_reference::( + let maybe_resolution = node_resolver.resolve_npm_req_reference( &npm_ref, NodeResolutionMode::Types, &mut PermissionsContainer::allow_all(), diff --git a/cli/worker.rs b/cli/worker.rs index c73e4edbed..e565789ede 100644 --- a/cli/worker.rs +++ b/cli/worker.rs @@ -1,7 +1,6 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. use std::path::PathBuf; -use std::rc::Rc; use std::sync::Arc; use deno_ast::ModuleSpecifier; @@ -14,7 +13,6 @@ use deno_core::ModuleId; use deno_runtime::colors; use deno_runtime::deno_node; use deno_runtime::deno_node::NodeResolution; -use deno_runtime::deno_node::RealFs; use deno_runtime::fmt_errors::format_js_error; use deno_runtime::ops::worker_host::CreateWebWorkerCb; use deno_runtime::ops::worker_host::WorkerEventCb; @@ -259,15 +257,13 @@ pub async fn create_custom_worker( ps.npm_resolver .add_package_reqs(vec![package_ref.req.clone()]) .await?; - let node_resolution = ps - .node_resolver - .resolve_binary_export::(&package_ref)?; + let node_resolution = + ps.node_resolver.resolve_binary_export(&package_ref)?; let is_main_cjs = matches!(node_resolution, NodeResolution::CommonJs(_)); (node_resolution.into_url(), is_main_cjs) } else if ps.options.is_npm_main() { - let node_resolution = ps - .node_resolver - .url_to_node_resolution::(main_module)?; + let node_resolution = + ps.node_resolver.url_to_node_resolution(main_module)?; let is_main_cjs = matches!(node_resolution, NodeResolution::CommonJs(_)); (node_resolution.into_url(), is_main_cjs) } else { @@ -345,7 +341,8 @@ pub async fn create_custom_worker( should_break_on_first_statement: ps.options.inspect_brk().is_some(), should_wait_for_inspector_session: ps.options.inspect_wait().is_some(), module_loader, - npm_resolver: Some(Rc::new(ps.npm_resolver.clone())), + node_fs: Some(ps.node_fs.clone()), + npm_resolver: Some(ps.npm_resolver.clone()), get_error_class_fn: Some(&errors::get_error_class_name), cache_storage_dir, origin_storage_dir, @@ -468,7 +465,8 @@ fn create_web_worker_callback( format_js_error_fn: Some(Arc::new(format_js_error)), source_map_getter: Some(Box::new(module_loader.clone())), module_loader, - npm_resolver: Some(Rc::new(ps.npm_resolver.clone())), + node_fs: Some(ps.node_fs.clone()), + npm_resolver: Some(ps.npm_resolver.clone()), worker_type: args.worker_type, maybe_inspector_server, get_error_class_fn: Some(&errors::get_error_class_name), @@ -492,6 +490,8 @@ fn create_web_worker_callback( #[cfg(test)] mod tests { + use std::rc::Rc; + use super::*; use deno_core::resolve_path; use deno_core::FsModuleLoader; @@ -520,6 +520,7 @@ mod tests { should_break_on_first_statement: false, should_wait_for_inspector_session: false, module_loader: Rc::new(FsModuleLoader), + node_fs: Some(Arc::new(deno_node::RealFs)), npm_resolver: None, get_error_class_fn: None, cache_storage_dir: None, diff --git a/ext/node/analyze.rs b/ext/node/analyze.rs index a206f4425a..f1af2f6110 100644 --- a/ext/node/analyze.rs +++ b/ext/node/analyze.rs @@ -5,6 +5,7 @@ use std::collections::VecDeque; use std::fmt::Write; use std::path::Path; use std::path::PathBuf; +use std::sync::Arc; use deno_core::anyhow::Context; use deno_core::ModuleSpecifier; @@ -12,11 +13,11 @@ use once_cell::sync::Lazy; use deno_core::error::AnyError; -use crate::package_exports_resolve; use crate::NodeFs; use crate::NodeModuleKind; use crate::NodePermissions; use crate::NodeResolutionMode; +use crate::NodeResolver; use crate::NpmResolver; use crate::PackageJson; use crate::PathClean; @@ -64,23 +65,26 @@ pub trait CjsEsmCodeAnalyzer { ) -> Result, AnyError>; } -pub struct NodeCodeTranslator< - TCjsEsmCodeAnalyzer: CjsEsmCodeAnalyzer, - TNpmResolver: NpmResolver, -> { +pub struct NodeCodeTranslator { cjs_esm_code_analyzer: TCjsEsmCodeAnalyzer, - npm_resolver: TNpmResolver, + fs: Arc, + node_resolver: Arc, + npm_resolver: Arc, } -impl - NodeCodeTranslator +impl + NodeCodeTranslator { pub fn new( cjs_esm_code_analyzer: TCjsEsmCodeAnalyzer, - npm_resolver: TNpmResolver, + fs: Arc, + node_resolver: Arc, + npm_resolver: Arc, ) -> Self { Self { cjs_esm_code_analyzer, + fs, + node_resolver, npm_resolver, } } @@ -105,7 +109,7 @@ impl /// For all discovered reexports the analysis will be performed recursively. /// /// If successful a source code for equivalent ES module is returned. - pub fn translate_cjs_to_esm( + pub fn translate_cjs_to_esm( &self, specifier: &ModuleSpecifier, source: &str, @@ -142,7 +146,7 @@ impl handled_reexports.insert(reexport.to_string()); // First, resolve relate reexport specifier - let resolved_reexport = self.resolve::( + let resolved_reexport = self.resolve( &reexport, &referrer, // FIXME(bartlomieju): check if these conditions are okay, probably @@ -154,7 +158,9 @@ impl // Second, read the source code from disk let reexport_specifier = ModuleSpecifier::from_file_path(&resolved_reexport).unwrap(); - let reexport_file_text = Fs::read_to_string(&resolved_reexport) + let reexport_file_text = self + .fs + .read_to_string(&resolved_reexport) .with_context(|| { format!( "Could not find '{}' ({}) referenced from {}", @@ -208,7 +214,7 @@ impl Ok(translated_source) } - fn resolve( + fn resolve( &self, specifier: &str, referrer: &ModuleSpecifier, @@ -223,10 +229,8 @@ impl let referrer_path = referrer.to_file_path().unwrap(); if specifier.starts_with("./") || specifier.starts_with("../") { if let Some(parent) = referrer_path.parent() { - return file_extension_probe::( - parent.join(specifier), - &referrer_path, - ); + return self + .file_extension_probe(parent.join(specifier), &referrer_path); } else { todo!(); } @@ -245,15 +249,16 @@ impl )?; let package_json_path = module_dir.join("package.json"); - if Fs::exists(&package_json_path) { - let package_json = PackageJson::load::( - &self.npm_resolver, + if self.fs.exists(&package_json_path) { + let package_json = PackageJson::load( + &*self.fs, + &*self.npm_resolver, permissions, package_json_path.clone(), )?; if let Some(exports) = &package_json.exports { - return package_exports_resolve::( + return self.node_resolver.package_exports_resolve( &package_json_path, package_subpath, exports, @@ -261,7 +266,6 @@ impl NodeModuleKind::Esm, conditions, mode, - &self.npm_resolver, permissions, ); } @@ -269,12 +273,13 @@ impl // old school if package_subpath != "." { let d = module_dir.join(package_subpath); - if Fs::is_dir(&d) { + if self.fs.is_dir(&d) { // subdir might have a package.json that specifies the entrypoint let package_json_path = d.join("package.json"); - if Fs::exists(&package_json_path) { - let package_json = PackageJson::load::( - &self.npm_resolver, + if self.fs.exists(&package_json_path) { + let package_json = PackageJson::load( + &*self.fs, + &*self.npm_resolver, permissions, package_json_path, )?; @@ -285,7 +290,7 @@ impl return Ok(d.join("index.js").clean()); } - return file_extension_probe::(d, &referrer_path); + return self.file_extension_probe(d, &referrer_path); } else if let Some(main) = package_json.main(NodeModuleKind::Cjs) { return Ok(module_dir.join(main).clean()); } else { @@ -294,6 +299,33 @@ impl } Err(not_found(specifier, &referrer_path)) } + + fn file_extension_probe( + &self, + p: PathBuf, + referrer: &Path, + ) -> Result { + let p = p.clean(); + if self.fs.exists(&p) { + let file_name = p.file_name().unwrap(); + let p_js = + p.with_file_name(format!("{}.js", file_name.to_str().unwrap())); + if self.fs.is_file(&p_js) { + return Ok(p_js); + } else if self.fs.is_dir(&p) { + return Ok(p.join("index.js")); + } else { + return Ok(p); + } + } else if let Some(file_name) = p.file_name() { + let p_js = + p.with_file_name(format!("{}.js", file_name.to_str().unwrap())); + if self.fs.is_file(&p_js) { + return Ok(p_js); + } + } + Err(not_found(&p.to_string_lossy(), referrer)) + } } fn esm_code_from_top_level_decls( @@ -455,30 +487,6 @@ fn parse_specifier(specifier: &str) -> Option<(String, String)> { Some((package_name, package_subpath)) } -fn file_extension_probe( - p: PathBuf, - referrer: &Path, -) -> Result { - let p = p.clean(); - if Fs::exists(&p) { - let file_name = p.file_name().unwrap(); - let p_js = p.with_file_name(format!("{}.js", file_name.to_str().unwrap())); - if Fs::is_file(&p_js) { - return Ok(p_js); - } else if Fs::is_dir(&p) { - return Ok(p.join("index.js")); - } else { - return Ok(p); - } - } else if let Some(file_name) = p.file_name() { - let p_js = p.with_file_name(format!("{}.js", file_name.to_str().unwrap())); - if Fs::is_file(&p_js) { - return Ok(p_js); - } - } - Err(not_found(&p.to_string_lossy(), referrer)) -} - fn not_found(path: &str, referrer: &Path) -> AnyError { let msg = format!( "[ERR_MODULE_NOT_FOUND] Cannot find module \"{}\" imported from \"{}\"", diff --git a/ext/node/lib.rs b/ext/node/lib.rs index 2b2ced89ce..e63c73537f 100644 --- a/ext/node/lib.rs +++ b/ext/node/lib.rs @@ -27,7 +27,6 @@ mod package_json; mod path; mod polyfill; mod resolution; -mod resolver; pub use package_json::PackageJson; pub use path::PathClean; @@ -35,22 +34,13 @@ pub use polyfill::is_builtin_node_module; pub use polyfill::resolve_builtin_node_module; pub use polyfill::NodeModulePolyfill; pub use polyfill::SUPPORTED_BUILTIN_NODE_MODULES; -pub use resolution::get_closest_package_json; -pub use resolution::get_package_scope_config; -pub use resolution::legacy_main_resolve; -pub use resolution::package_exports_resolve; -pub use resolution::package_imports_resolve; -pub use resolution::package_resolve; -pub use resolution::path_to_declaration_path; pub use resolution::NodeModuleKind; +pub use resolution::NodeResolution; pub use resolution::NodeResolutionMode; -pub use resolution::DEFAULT_CONDITIONS; -pub use resolver::NodeResolution; -pub use resolver::NodeResolver; +pub use resolution::NodeResolver; pub trait NodeEnv { type P: NodePermissions; - type Fs: NodeFs; } pub trait NodePermissions { @@ -71,24 +61,26 @@ pub struct NodeFsMetadata { pub is_dir: bool, } -pub trait NodeFs { - fn current_dir() -> io::Result; - fn metadata>(path: P) -> io::Result; - fn is_file>(path: P) -> bool; - fn is_dir>(path: P) -> bool; - fn exists>(path: P) -> bool; - fn read_to_string>(path: P) -> io::Result; - fn canonicalize>(path: P) -> io::Result; +pub trait NodeFs: std::fmt::Debug + Send + Sync { + fn current_dir(&self) -> io::Result; + fn metadata(&self, path: &Path) -> io::Result; + fn is_file(&self, path: &Path) -> bool; + fn is_dir(&self, path: &Path) -> bool; + fn exists(&self, path: &Path) -> bool; + fn read_to_string(&self, path: &Path) -> io::Result; + fn canonicalize(&self, path: &Path) -> io::Result; } +#[derive(Debug)] pub struct RealFs; + impl NodeFs for RealFs { - fn current_dir() -> io::Result { + fn current_dir(&self) -> io::Result { #[allow(clippy::disallowed_methods)] std::env::current_dir() } - fn metadata>(path: P) -> io::Result { + fn metadata(&self, path: &Path) -> io::Result { #[allow(clippy::disallowed_methods)] std::fs::metadata(path).map(|metadata| { // on most systems, calling is_file() and is_dir() is cheap @@ -100,35 +92,35 @@ impl NodeFs for RealFs { }) } - fn exists>(path: P) -> bool { + fn exists(&self, path: &Path) -> bool { #[allow(clippy::disallowed_methods)] std::fs::metadata(path).is_ok() } - fn is_file>(path: P) -> bool { + fn is_file(&self, path: &Path) -> bool { #[allow(clippy::disallowed_methods)] std::fs::metadata(path) .map(|m| m.is_file()) .unwrap_or(false) } - fn is_dir>(path: P) -> bool { + fn is_dir(&self, path: &Path) -> bool { #[allow(clippy::disallowed_methods)] std::fs::metadata(path).map(|m| m.is_dir()).unwrap_or(false) } - fn read_to_string>(path: P) -> io::Result { + fn read_to_string(&self, path: &Path) -> io::Result { #[allow(clippy::disallowed_methods)] std::fs::read_to_string(path) } - fn canonicalize>(path: P) -> io::Result { + fn canonicalize(&self, path: &Path) -> io::Result { #[allow(clippy::disallowed_methods)] - std::path::Path::canonicalize(path.as_ref()) + std::path::Path::canonicalize(path) } } -pub trait NpmResolver { +pub trait NpmResolver: std::fmt::Debug + Send + Sync { /// Resolves an npm package folder path from an npm package referrer. fn resolve_package_folder_from_package( &self, @@ -177,57 +169,6 @@ pub trait NpmResolver { ) -> Result<(), AnyError>; } -impl NpmResolver for Arc { - fn resolve_package_folder_from_package( - &self, - specifier: &str, - referrer: &ModuleSpecifier, - mode: NodeResolutionMode, - ) -> Result { - (**self).resolve_package_folder_from_package(specifier, referrer, mode) - } - - fn resolve_package_folder_from_path( - &self, - path: &Path, - ) -> Result { - (**self).resolve_package_folder_from_path(path) - } - - fn resolve_package_folder_from_deno_module( - &self, - pkg_nv: &NpmPackageNv, - ) -> Result { - (**self).resolve_package_folder_from_deno_module(pkg_nv) - } - - fn resolve_pkg_id_from_pkg_req( - &self, - req: &NpmPackageReq, - ) -> Result { - (**self).resolve_pkg_id_from_pkg_req(req) - } - - fn resolve_nv_ref_from_pkg_req_ref( - &self, - req_ref: &NpmPackageReqReference, - ) -> Result { - (**self).resolve_nv_ref_from_pkg_req_ref(req_ref) - } - - fn in_npm_package(&self, specifier: &ModuleSpecifier) -> bool { - (**self).in_npm_package(specifier) - } - - fn ensure_read_permission( - &self, - permissions: &mut dyn NodePermissions, - path: &Path, - ) -> Result<(), AnyError> { - (**self).ensure_read_permission(permissions, path) - } -} - pub static NODE_GLOBAL_THIS_NAME: Lazy = Lazy::new(|| { let now = std::time::SystemTime::now(); let seconds = now @@ -582,11 +523,18 @@ deno_core::extension!(deno_node, "zlib.ts", ], options = { - maybe_npm_resolver: Option>, + maybe_npm_resolver: Option>, + fs: Option>, }, state = |state, options| { + let fs = options.fs.unwrap_or_else(|| Arc::new(RealFs)); + state.put(fs.clone()); if let Some(npm_resolver) = options.maybe_npm_resolver { - state.put(npm_resolver); + state.put(npm_resolver.clone()); + state.put(Rc::new(NodeResolver::new( + fs, + npm_resolver, + ))) } }, ); diff --git a/ext/node/ops/require.rs b/ext/node/ops/require.rs index 82a0433400..513b3f5899 100644 --- a/ext/node/ops/require.rs +++ b/ext/node/ops/require.rs @@ -13,6 +13,7 @@ use std::cell::RefCell; use std::path::Path; use std::path::PathBuf; use std::rc::Rc; +use std::sync::Arc; use crate::resolution; use crate::NodeEnv; @@ -20,6 +21,7 @@ use crate::NodeFs; use crate::NodeModuleKind; use crate::NodePermissions; use crate::NodeResolutionMode; +use crate::NodeResolver; use crate::NpmResolver; use crate::PackageJson; @@ -31,7 +33,7 @@ where P: NodePermissions + 'static, { let resolver = { - let resolver = state.borrow::>(); + let resolver = state.borrow::>(); resolver.clone() }; let permissions = state.borrow_mut::

(); @@ -96,10 +98,11 @@ pub fn op_require_node_module_paths( where Env: NodeEnv + 'static, { + let fs = state.borrow::>().clone(); // Guarantee that "from" is absolute. let from = deno_core::resolve_path( &from, - &(Env::Fs::current_dir()).context("Unable to get CWD")?, + &(fs.current_dir()).context("Unable to get CWD")?, ) .unwrap() .to_file_path() @@ -191,7 +194,7 @@ fn op_require_resolve_deno_dir( request: String, parent_filename: String, ) -> Option { - let resolver = state.borrow::>(); + let resolver = state.borrow::>(); resolver .resolve_package_folder_from_package( &request, @@ -204,7 +207,7 @@ fn op_require_resolve_deno_dir( #[op] fn op_require_is_deno_dir_package(state: &mut OpState, path: String) -> bool { - let resolver = state.borrow::>(); + let resolver = state.borrow::>(); resolver.in_npm_package_at_path(&PathBuf::from(path)) } @@ -264,7 +267,8 @@ where { let path = PathBuf::from(path); ensure_read_permission::(state, &path)?; - if let Ok(metadata) = Env::Fs::metadata(&path) { + let fs = state.borrow::>().clone(); + if let Ok(metadata) = fs.metadata(&path) { if metadata.is_file { return Ok(0); } else { @@ -285,7 +289,8 @@ where { let path = PathBuf::from(request); ensure_read_permission::(state, &path)?; - let mut canonicalized_path = Env::Fs::canonicalize(&path)?; + let fs = state.borrow::>().clone(); + let mut canonicalized_path = fs.canonicalize(&path)?; if cfg!(windows) { canonicalized_path = PathBuf::from( canonicalized_path @@ -353,7 +358,8 @@ where if let Some(parent_id) = maybe_parent_id { if parent_id == "" || parent_id == "internal/preload" { - if let Ok(cwd) = Env::Fs::current_dir() { + let fs = state.borrow::>().clone(); + if let Ok(cwd) = fs.current_dir() { ensure_read_permission::(state, &cwd)?; return Ok(Some(cwd.to_string_lossy().to_string())); } @@ -375,14 +381,14 @@ where return Ok(None); } - let resolver = state.borrow::>().clone(); + let node_resolver = state.borrow::>().clone(); let permissions = state.borrow_mut::(); - let pkg = resolution::get_package_scope_config::( - &Url::from_file_path(parent_path.unwrap()).unwrap(), - &*resolver, - permissions, - ) - .ok(); + let pkg = node_resolver + .get_package_scope_config( + &Url::from_file_path(parent_path.unwrap()).unwrap(), + permissions, + ) + .ok(); if pkg.is_none() { return Ok(None); } @@ -408,18 +414,18 @@ where let referrer = deno_core::url::Url::from_file_path(&pkg.path).unwrap(); if let Some(exports) = &pkg.exports { - resolution::package_exports_resolve::( - &pkg.path, - expansion, - exports, - &referrer, - NodeModuleKind::Cjs, - resolution::REQUIRE_CONDITIONS, - NodeResolutionMode::Execution, - &*resolver, - permissions, - ) - .map(|r| Some(r.to_string_lossy().to_string())) + node_resolver + .package_exports_resolve( + &pkg.path, + expansion, + exports, + &referrer, + NodeModuleKind::Cjs, + resolution::REQUIRE_CONDITIONS, + NodeResolutionMode::Execution, + permissions, + ) + .map(|r| Some(r.to_string_lossy().to_string())) } else { Ok(None) } @@ -435,7 +441,8 @@ where { let file_path = PathBuf::from(file_path); ensure_read_permission::(state, &file_path)?; - Ok(Env::Fs::read_to_string(file_path)?) + let fs = state.borrow::>().clone(); + Ok(fs.read_to_string(&file_path)?) } #[op] @@ -462,10 +469,12 @@ fn op_require_resolve_exports( where Env: NodeEnv + 'static, { - let resolver = state.borrow::>().clone(); + let fs = state.borrow::>().clone(); + let npm_resolver = state.borrow::>().clone(); + let node_resolver = state.borrow::>().clone(); let permissions = state.borrow_mut::(); - let pkg_path = if resolver + let pkg_path = if npm_resolver .in_npm_package_at_path(&PathBuf::from(&modules_path)) && !uses_local_node_modules_dir { @@ -473,32 +482,31 @@ where } else { let orignal = modules_path.clone(); let mod_dir = path_resolve(vec![modules_path, name]); - if Env::Fs::is_dir(&mod_dir) { + if fs.is_dir(Path::new(&mod_dir)) { mod_dir } else { orignal } }; - let pkg = PackageJson::load::( - &*resolver, + let pkg = node_resolver.load_package_json( permissions, PathBuf::from(&pkg_path).join("package.json"), )?; if let Some(exports) = &pkg.exports { let referrer = Url::from_file_path(parent_path).unwrap(); - resolution::package_exports_resolve::( - &pkg.path, - format!(".{expansion}"), - exports, - &referrer, - NodeModuleKind::Cjs, - resolution::REQUIRE_CONDITIONS, - NodeResolutionMode::Execution, - &*resolver, - permissions, - ) - .map(|r| Some(r.to_string_lossy().to_string())) + node_resolver + .package_exports_resolve( + &pkg.path, + format!(".{expansion}"), + exports, + &referrer, + NodeModuleKind::Cjs, + resolution::REQUIRE_CONDITIONS, + NodeResolutionMode::Execution, + permissions, + ) + .map(|r| Some(r.to_string_lossy().to_string())) } else { Ok(None) } @@ -516,11 +524,10 @@ where state, PathBuf::from(&filename).parent().unwrap(), )?; - let resolver = state.borrow::>().clone(); + let node_resolver = state.borrow::>().clone(); let permissions = state.borrow_mut::(); - resolution::get_closest_package_json::( + node_resolver.get_closest_package_json( &Url::from_file_path(filename).unwrap(), - &*resolver, permissions, ) } @@ -533,10 +540,12 @@ fn op_require_read_package_scope( where Env: NodeEnv + 'static, { - let resolver = state.borrow::>().clone(); + let node_resolver = state.borrow::>().clone(); let permissions = state.borrow_mut::(); let package_json_path = PathBuf::from(package_json_path); - PackageJson::load::(&*resolver, permissions, package_json_path).ok() + node_resolver + .load_package_json(permissions, package_json_path) + .ok() } #[op] @@ -550,29 +559,24 @@ where { let parent_path = PathBuf::from(&parent_filename); ensure_read_permission::(state, &parent_path)?; - let resolver = state.borrow::>().clone(); + let node_resolver = state.borrow::>().clone(); let permissions = state.borrow_mut::(); - let pkg = PackageJson::load::( - &*resolver, - permissions, - parent_path.join("package.json"), - )?; + let pkg = node_resolver + .load_package_json(permissions, parent_path.join("package.json"))?; if pkg.imports.is_some() { let referrer = deno_core::url::Url::from_file_path(&parent_filename).unwrap(); - let r = resolution::package_imports_resolve::( - &request, - &referrer, - NodeModuleKind::Cjs, - resolution::REQUIRE_CONDITIONS, - NodeResolutionMode::Execution, - &*resolver, - permissions, - ) - .map(|r| Some(Url::from_file_path(r).unwrap().to_string())); - state.put(resolver); - r + node_resolver + .package_imports_resolve( + &request, + &referrer, + NodeModuleKind::Cjs, + resolution::REQUIRE_CONDITIONS, + NodeResolutionMode::Execution, + permissions, + ) + .map(|r| Some(Url::from_file_path(r).unwrap().to_string())) } else { Ok(None) } diff --git a/ext/node/package_json.rs b/ext/node/package_json.rs index 08f78681ae..0e34897e3a 100644 --- a/ext/node/package_json.rs +++ b/ext/node/package_json.rs @@ -62,16 +62,18 @@ impl PackageJson { } } - pub fn load( + pub fn load( + fs: &dyn NodeFs, resolver: &dyn NpmResolver, permissions: &mut dyn NodePermissions, path: PathBuf, ) -> Result { resolver.ensure_read_permission(permissions, &path)?; - Self::load_skip_read_permission::(path) + Self::load_skip_read_permission(fs, path) } - pub fn load_skip_read_permission( + pub fn load_skip_read_permission( + fs: &dyn NodeFs, path: PathBuf, ) -> Result { assert!(path.is_absolute()); @@ -80,7 +82,7 @@ impl PackageJson { return Ok(CACHE.with(|cache| cache.borrow()[&path].clone())); } - let source = match Fs::read_to_string(&path) { + let source = match fs.read_to_string(&path) { Ok(source) => source, Err(err) if err.kind() == ErrorKind::NotFound => { return Ok(PackageJson::empty(path)); diff --git a/ext/node/resolution.rs b/ext/node/resolution.rs index d324f4b4b9..e5db6b3ac7 100644 --- a/ext/node/resolution.rs +++ b/ext/node/resolution.rs @@ -2,21 +2,28 @@ use std::path::Path; use std::path::PathBuf; +use std::sync::Arc; use deno_core::anyhow::bail; +use deno_core::anyhow::Context; use deno_core::error::generic_error; use deno_core::error::AnyError; use deno_core::serde_json::Map; use deno_core::serde_json::Value; use deno_core::url::Url; use deno_core::ModuleSpecifier; +use deno_media_type::MediaType; +use deno_semver::npm::NpmPackageNv; +use deno_semver::npm::NpmPackageNvReference; +use deno_semver::npm::NpmPackageReqReference; use crate::errors; -use crate::package_json::PackageJson; -use crate::path::PathClean; +use crate::AllowAllNodePermissions; use crate::NodeFs; use crate::NodePermissions; use crate::NpmResolver; +use crate::PackageJson; +use crate::PathClean; pub static DEFAULT_CONDITIONS: &[&str] = &["deno", "node", "import"]; pub static REQUIRE_CONDITIONS: &[&str] = &["require", "node"]; @@ -39,53 +46,1260 @@ impl NodeResolutionMode { } } -/// Checks if the resolved file has a corresponding declaration file. -pub fn path_to_declaration_path( - path: PathBuf, - referrer_kind: NodeModuleKind, -) -> Option { - fn probe_extensions( - path: &Path, - referrer_kind: NodeModuleKind, - ) -> Option { - let specific_dts_path = match referrer_kind { - NodeModuleKind::Cjs => with_known_extension(path, "d.cts"), - NodeModuleKind::Esm => with_known_extension(path, "d.mts"), - }; - if Fs::exists(&specific_dts_path) { - return Some(specific_dts_path); - } - let dts_path = with_known_extension(path, "d.ts"); - if Fs::exists(&dts_path) { - Some(dts_path) - } else { - None +#[derive(Debug)] +pub enum NodeResolution { + Esm(ModuleSpecifier), + CommonJs(ModuleSpecifier), + BuiltIn(String), +} + +impl NodeResolution { + pub fn into_url(self) -> ModuleSpecifier { + match self { + Self::Esm(u) => u, + Self::CommonJs(u) => u, + Self::BuiltIn(specifier) => { + if specifier.starts_with("node:") { + ModuleSpecifier::parse(&specifier).unwrap() + } else { + ModuleSpecifier::parse(&format!("node:{specifier}")).unwrap() + } + } } } - let lowercase_path = path.to_string_lossy().to_lowercase(); - if lowercase_path.ends_with(".d.ts") - || lowercase_path.ends_with(".d.cts") - || lowercase_path.ends_with(".d.ts") - { - return Some(path); + pub fn into_specifier_and_media_type( + resolution: Option, + ) -> (ModuleSpecifier, MediaType) { + match resolution { + Some(NodeResolution::CommonJs(specifier)) => { + let media_type = MediaType::from_specifier(&specifier); + ( + specifier, + match media_type { + MediaType::JavaScript | MediaType::Jsx => MediaType::Cjs, + MediaType::TypeScript | MediaType::Tsx => MediaType::Cts, + MediaType::Dts => MediaType::Dcts, + _ => media_type, + }, + ) + } + Some(NodeResolution::Esm(specifier)) => { + let media_type = MediaType::from_specifier(&specifier); + ( + specifier, + match media_type { + MediaType::JavaScript | MediaType::Jsx => MediaType::Mjs, + MediaType::TypeScript | MediaType::Tsx => MediaType::Mts, + MediaType::Dts => MediaType::Dmts, + _ => media_type, + }, + ) + } + Some(resolution) => (resolution.into_url(), MediaType::Dts), + None => ( + ModuleSpecifier::parse("internal:///missing_dependency.d.ts").unwrap(), + MediaType::Dts, + ), + } } - if let Some(path) = probe_extensions::(&path, referrer_kind) { - return Some(path); +} + +#[derive(Debug)] +pub struct NodeResolver { + fs: Arc, + npm_resolver: Arc, +} + +impl NodeResolver { + pub fn new(fs: Arc, npm_resolver: Arc) -> Self { + Self { fs, npm_resolver } } - if Fs::is_dir(&path) { - if let Some(path) = - probe_extensions::(&path.join("index"), referrer_kind) + + pub fn in_npm_package(&self, specifier: &ModuleSpecifier) -> bool { + self.npm_resolver.in_npm_package(specifier) + } + + /// This function is an implementation of `defaultResolve` in + /// `lib/internal/modules/esm/resolve.js` from Node. + pub fn resolve( + &self, + specifier: &str, + referrer: &ModuleSpecifier, + mode: NodeResolutionMode, + permissions: &mut dyn NodePermissions, + ) -> Result, AnyError> { + // Note: if we are here, then the referrer is an esm module + // TODO(bartlomieju): skipped "policy" part as we don't plan to support it + + if crate::is_builtin_node_module(specifier) { + return Ok(Some(NodeResolution::BuiltIn(specifier.to_string()))); + } + + if let Ok(url) = Url::parse(specifier) { + if url.scheme() == "data" { + return Ok(Some(NodeResolution::Esm(url))); + } + + let protocol = url.scheme(); + + if protocol == "node" { + let split_specifier = url.as_str().split(':'); + let specifier = split_specifier.skip(1).collect::(); + + if crate::is_builtin_node_module(&specifier) { + return Ok(Some(NodeResolution::BuiltIn(specifier))); + } + } + + if protocol != "file" && protocol != "data" { + return Err(errors::err_unsupported_esm_url_scheme(&url)); + } + + // todo(dsherret): this seems wrong + if referrer.scheme() == "data" { + let url = referrer.join(specifier).map_err(AnyError::from)?; + return Ok(Some(NodeResolution::Esm(url))); + } + } + + let url = self.module_resolve( + specifier, + referrer, + DEFAULT_CONDITIONS, + mode, + permissions, + )?; + let url = match url { + Some(url) => url, + None => return Ok(None), + }; + let url = match mode { + NodeResolutionMode::Execution => url, + NodeResolutionMode::Types => { + let path = url.to_file_path().unwrap(); + // todo(16370): the module kind is not correct here. I think we need + // typescript to tell us if the referrer is esm or cjs + let path = + match self.path_to_declaration_path(path, NodeModuleKind::Esm) { + Some(path) => path, + None => return Ok(None), + }; + ModuleSpecifier::from_file_path(path).unwrap() + } + }; + + let resolve_response = self.url_to_node_resolution(url)?; + // TODO(bartlomieju): skipped checking errors for commonJS resolution and + // "preserveSymlinksMain"/"preserveSymlinks" options. + Ok(Some(resolve_response)) + } + + fn module_resolve( + &self, + specifier: &str, + referrer: &ModuleSpecifier, + conditions: &[&str], + mode: NodeResolutionMode, + permissions: &mut dyn NodePermissions, + ) -> Result, AnyError> { + // note: if we're here, the referrer is an esm module + let url = if should_be_treated_as_relative_or_absolute_path(specifier) { + let resolved_specifier = referrer.join(specifier)?; + if mode.is_types() { + let file_path = to_file_path(&resolved_specifier); + // todo(dsherret): the node module kind is not correct and we + // should use the value provided by typescript instead + let declaration_path = + self.path_to_declaration_path(file_path, NodeModuleKind::Esm); + declaration_path.map(|declaration_path| { + ModuleSpecifier::from_file_path(declaration_path).unwrap() + }) + } else { + Some(resolved_specifier) + } + } else if specifier.starts_with('#') { + Some( + self + .package_imports_resolve( + specifier, + referrer, + NodeModuleKind::Esm, + conditions, + mode, + permissions, + ) + .map(|p| ModuleSpecifier::from_file_path(p).unwrap())?, + ) + } else if let Ok(resolved) = Url::parse(specifier) { + Some(resolved) + } else { + self + .package_resolve( + specifier, + referrer, + NodeModuleKind::Esm, + conditions, + mode, + permissions, + )? + .map(|p| ModuleSpecifier::from_file_path(p).unwrap()) + }; + Ok(match url { + Some(url) => Some(self.finalize_resolution(url, referrer)?), + None => None, + }) + } + + fn finalize_resolution( + &self, + resolved: ModuleSpecifier, + base: &ModuleSpecifier, + ) -> Result { + let encoded_sep_re = lazy_regex::regex!(r"%2F|%2C"); + + if encoded_sep_re.is_match(resolved.path()) { + return Err(errors::err_invalid_module_specifier( + resolved.path(), + "must not include encoded \"/\" or \"\\\\\" characters", + Some(to_file_path_string(base)), + )); + } + + let path = to_file_path(&resolved); + + // TODO(bartlomieju): currently not supported + // if (getOptionValue('--experimental-specifier-resolution') === 'node') { + // ... + // } + + let p_str = path.to_str().unwrap(); + let p = if p_str.ends_with('/') { + p_str[p_str.len() - 1..].to_string() + } else { + p_str.to_string() + }; + + let (is_dir, is_file) = if let Ok(stats) = self.fs.metadata(Path::new(&p)) { + (stats.is_dir, stats.is_file) + } else { + (false, false) + }; + if is_dir { + return Err(errors::err_unsupported_dir_import( + resolved.as_str(), + base.as_str(), + )); + } else if !is_file { + return Err(errors::err_module_not_found( + resolved.as_str(), + base.as_str(), + "module", + )); + } + + Ok(resolved) + } + + pub fn resolve_npm_req_reference( + &self, + reference: &NpmPackageReqReference, + mode: NodeResolutionMode, + permissions: &mut dyn NodePermissions, + ) -> Result, AnyError> { + let reference = self + .npm_resolver + .resolve_nv_ref_from_pkg_req_ref(reference)?; + self.resolve_npm_reference(&reference, mode, permissions) + } + + pub fn resolve_npm_reference( + &self, + reference: &NpmPackageNvReference, + mode: NodeResolutionMode, + permissions: &mut dyn NodePermissions, + ) -> Result, AnyError> { + let package_folder = self + .npm_resolver + .resolve_package_folder_from_deno_module(&reference.nv)?; + let node_module_kind = NodeModuleKind::Esm; + let maybe_resolved_path = self + .package_config_resolve( + &reference + .sub_path + .as_ref() + .map(|s| format!("./{s}")) + .unwrap_or_else(|| ".".to_string()), + &package_folder, + node_module_kind, + DEFAULT_CONDITIONS, + mode, + permissions, + ) + .with_context(|| { + format!("Error resolving package config for '{reference}'") + })?; + let resolved_path = match maybe_resolved_path { + Some(resolved_path) => resolved_path, + None => return Ok(None), + }; + let resolved_path = match mode { + NodeResolutionMode::Execution => resolved_path, + NodeResolutionMode::Types => { + match self.path_to_declaration_path(resolved_path, node_module_kind) { + Some(path) => path, + None => return Ok(None), + } + } + }; + let url = ModuleSpecifier::from_file_path(resolved_path).unwrap(); + let resolve_response = self.url_to_node_resolution(url)?; + // TODO(bartlomieju): skipped checking errors for commonJS resolution and + // "preserveSymlinksMain"/"preserveSymlinks" options. + Ok(Some(resolve_response)) + } + + pub fn resolve_binary_commands( + &self, + pkg_nv: &NpmPackageNv, + ) -> Result, AnyError> { + let package_folder = self + .npm_resolver + .resolve_package_folder_from_deno_module(pkg_nv)?; + let package_json_path = package_folder.join("package.json"); + let package_json = self + .load_package_json(&mut AllowAllNodePermissions, package_json_path)?; + + Ok(match package_json.bin { + Some(Value::String(_)) => vec![pkg_nv.name.to_string()], + Some(Value::Object(o)) => { + o.into_iter().map(|(key, _)| key).collect::>() + } + _ => Vec::new(), + }) + } + + pub fn resolve_binary_export( + &self, + pkg_ref: &NpmPackageReqReference, + ) -> Result { + let pkg_nv = self + .npm_resolver + .resolve_pkg_id_from_pkg_req(&pkg_ref.req)? + .nv; + let bin_name = pkg_ref.sub_path.as_deref(); + let package_folder = self + .npm_resolver + .resolve_package_folder_from_deno_module(&pkg_nv)?; + let package_json_path = package_folder.join("package.json"); + let package_json = self + .load_package_json(&mut AllowAllNodePermissions, package_json_path)?; + let bin = match &package_json.bin { + Some(bin) => bin, + None => bail!( + "package '{}' did not have a bin property in its package.json", + &pkg_nv.name, + ), + }; + let bin_entry = resolve_bin_entry_value(&pkg_nv, bin_name, bin)?; + let url = + ModuleSpecifier::from_file_path(package_folder.join(bin_entry)).unwrap(); + + let resolve_response = self.url_to_node_resolution(url)?; + // TODO(bartlomieju): skipped checking errors for commonJS resolution and + // "preserveSymlinksMain"/"preserveSymlinks" options. + Ok(resolve_response) + } + + pub fn url_to_node_resolution( + &self, + url: ModuleSpecifier, + ) -> Result { + let url_str = url.as_str().to_lowercase(); + if url_str.starts_with("http") { + Ok(NodeResolution::Esm(url)) + } else if url_str.ends_with(".js") || url_str.ends_with(".d.ts") { + let package_config = + self.get_closest_package_json(&url, &mut AllowAllNodePermissions)?; + if package_config.typ == "module" { + Ok(NodeResolution::Esm(url)) + } else { + Ok(NodeResolution::CommonJs(url)) + } + } else if url_str.ends_with(".mjs") || url_str.ends_with(".d.mts") { + Ok(NodeResolution::Esm(url)) + } else if url_str.ends_with(".ts") { + Err(generic_error(format!( + "TypeScript files are not supported in npm packages: {url}" + ))) + } else { + Ok(NodeResolution::CommonJs(url)) + } + } + + fn package_config_resolve( + &self, + package_subpath: &str, + package_dir: &Path, + referrer_kind: NodeModuleKind, + conditions: &[&str], + mode: NodeResolutionMode, + permissions: &mut dyn NodePermissions, + ) -> Result, AnyError> { + let package_json_path = package_dir.join("package.json"); + let referrer = ModuleSpecifier::from_directory_path(package_dir).unwrap(); + let package_config = + self.load_package_json(permissions, package_json_path.clone())?; + if let Some(exports) = &package_config.exports { + let result = self.package_exports_resolve( + &package_json_path, + package_subpath.to_string(), + exports, + &referrer, + referrer_kind, + conditions, + mode, + permissions, + ); + match result { + Ok(found) => return Ok(Some(found)), + Err(exports_err) => { + if mode.is_types() && package_subpath == "." { + if let Ok(Some(path)) = + self.legacy_main_resolve(&package_config, referrer_kind, mode) + { + return Ok(Some(path)); + } else { + return Ok(None); + } + } + return Err(exports_err); + } + } + } + if package_subpath == "." { + return self.legacy_main_resolve(&package_config, referrer_kind, mode); + } + + Ok(Some(package_dir.join(package_subpath))) + } + + /// Checks if the resolved file has a corresponding declaration file. + pub(super) fn path_to_declaration_path( + &self, + path: PathBuf, + referrer_kind: NodeModuleKind, + ) -> Option { + fn probe_extensions( + fs: &dyn NodeFs, + path: &Path, + referrer_kind: NodeModuleKind, + ) -> Option { + let specific_dts_path = match referrer_kind { + NodeModuleKind::Cjs => with_known_extension(path, "d.cts"), + NodeModuleKind::Esm => with_known_extension(path, "d.mts"), + }; + if fs.exists(&specific_dts_path) { + return Some(specific_dts_path); + } + let dts_path = with_known_extension(path, "d.ts"); + if fs.exists(&dts_path) { + Some(dts_path) + } else { + None + } + } + + let lowercase_path = path.to_string_lossy().to_lowercase(); + if lowercase_path.ends_with(".d.ts") + || lowercase_path.ends_with(".d.cts") + || lowercase_path.ends_with(".d.ts") { return Some(path); } + if let Some(path) = probe_extensions(&*self.fs, &path, referrer_kind) { + return Some(path); + } + if self.fs.is_dir(&path) { + if let Some(path) = + probe_extensions(&*self.fs, &path.join("index"), referrer_kind) + { + return Some(path); + } + } + None } - None + + pub(super) fn package_imports_resolve( + &self, + name: &str, + referrer: &ModuleSpecifier, + referrer_kind: NodeModuleKind, + conditions: &[&str], + mode: NodeResolutionMode, + permissions: &mut dyn NodePermissions, + ) -> Result { + if name == "#" || name.starts_with("#/") || name.ends_with('/') { + let reason = "is not a valid internal imports specifier name"; + return Err(errors::err_invalid_module_specifier( + name, + reason, + Some(to_specifier_display_string(referrer)), + )); + } + + let package_config = + self.get_package_scope_config(referrer, permissions)?; + let mut package_json_path = None; + if package_config.exists { + package_json_path = Some(package_config.path.clone()); + if let Some(imports) = &package_config.imports { + if imports.contains_key(name) && !name.contains('*') { + let maybe_resolved = self.resolve_package_target( + package_json_path.as_ref().unwrap(), + imports.get(name).unwrap().to_owned(), + "".to_string(), + name.to_string(), + referrer, + referrer_kind, + false, + true, + conditions, + mode, + permissions, + )?; + if let Some(resolved) = maybe_resolved { + return Ok(resolved); + } + } else { + let mut best_match = ""; + let mut best_match_subpath = None; + for key in imports.keys() { + let pattern_index = key.find('*'); + if let Some(pattern_index) = pattern_index { + let key_sub = &key[0..=pattern_index]; + if name.starts_with(key_sub) { + let pattern_trailer = &key[pattern_index + 1..]; + if name.len() > key.len() + && name.ends_with(&pattern_trailer) + && pattern_key_compare(best_match, key) == 1 + && key.rfind('*') == Some(pattern_index) + { + best_match = key; + best_match_subpath = Some( + name[pattern_index..=(name.len() - pattern_trailer.len())] + .to_string(), + ); + } + } + } + } + + if !best_match.is_empty() { + let target = imports.get(best_match).unwrap().to_owned(); + let maybe_resolved = self.resolve_package_target( + package_json_path.as_ref().unwrap(), + target, + best_match_subpath.unwrap(), + best_match.to_string(), + referrer, + referrer_kind, + true, + true, + conditions, + mode, + permissions, + )?; + if let Some(resolved) = maybe_resolved { + return Ok(resolved); + } + } + } + } + } + + Err(throw_import_not_defined( + name, + package_json_path.as_deref(), + referrer, + )) + } + + #[allow(clippy::too_many_arguments)] + fn resolve_package_target_string( + &self, + target: String, + subpath: String, + match_: String, + package_json_path: &Path, + referrer: &ModuleSpecifier, + referrer_kind: NodeModuleKind, + pattern: bool, + internal: bool, + conditions: &[&str], + mode: NodeResolutionMode, + permissions: &mut dyn NodePermissions, + ) -> Result { + if !subpath.is_empty() && !pattern && !target.ends_with('/') { + return Err(throw_invalid_package_target( + match_, + target, + package_json_path, + internal, + referrer, + )); + } + let invalid_segment_re = + lazy_regex::regex!(r"(^|\\|/)(\.\.?|node_modules)(\\|/|$)"); + let pattern_re = lazy_regex::regex!(r"\*"); + if !target.starts_with("./") { + if internal && !target.starts_with("../") && !target.starts_with('/') { + let is_url = Url::parse(&target).is_ok(); + if !is_url { + let export_target = if pattern { + pattern_re + .replace(&target, |_caps: ®ex::Captures| subpath.clone()) + .to_string() + } else { + format!("{target}{subpath}") + }; + let package_json_url = + ModuleSpecifier::from_file_path(package_json_path).unwrap(); + return match self.package_resolve( + &export_target, + &package_json_url, + referrer_kind, + conditions, + mode, + permissions, + ) { + Ok(Some(path)) => Ok(path), + Ok(None) => Err(generic_error("not found")), + Err(err) => Err(err), + }; + } + } + return Err(throw_invalid_package_target( + match_, + target, + package_json_path, + internal, + referrer, + )); + } + if invalid_segment_re.is_match(&target[2..]) { + return Err(throw_invalid_package_target( + match_, + target, + package_json_path, + internal, + referrer, + )); + } + let package_path = package_json_path.parent().unwrap(); + let resolved_path = package_path.join(&target).clean(); + if !resolved_path.starts_with(package_path) { + return Err(throw_invalid_package_target( + match_, + target, + package_json_path, + internal, + referrer, + )); + } + if subpath.is_empty() { + return Ok(resolved_path); + } + if invalid_segment_re.is_match(&subpath) { + let request = if pattern { + match_.replace('*', &subpath) + } else { + format!("{match_}{subpath}") + }; + return Err(throw_invalid_subpath( + request, + package_json_path, + internal, + referrer, + )); + } + if pattern { + let resolved_path_str = resolved_path.to_string_lossy(); + let replaced = pattern_re + .replace(&resolved_path_str, |_caps: ®ex::Captures| { + subpath.clone() + }); + return Ok(PathBuf::from(replaced.to_string())); + } + Ok(resolved_path.join(&subpath).clean()) + } + + #[allow(clippy::too_many_arguments)] + fn resolve_package_target( + &self, + package_json_path: &Path, + target: Value, + subpath: String, + package_subpath: String, + referrer: &ModuleSpecifier, + referrer_kind: NodeModuleKind, + pattern: bool, + internal: bool, + conditions: &[&str], + mode: NodeResolutionMode, + permissions: &mut dyn NodePermissions, + ) -> Result, AnyError> { + if let Some(target) = target.as_str() { + return self + .resolve_package_target_string( + target.to_string(), + subpath, + package_subpath, + package_json_path, + referrer, + referrer_kind, + pattern, + internal, + conditions, + mode, + permissions, + ) + .map(|path| { + if mode.is_types() { + self.path_to_declaration_path(path, referrer_kind) + } else { + Some(path) + } + }); + } else if let Some(target_arr) = target.as_array() { + if target_arr.is_empty() { + return Ok(None); + } + + let mut last_error = None; + for target_item in target_arr { + let resolved_result = self.resolve_package_target( + package_json_path, + target_item.to_owned(), + subpath.clone(), + package_subpath.clone(), + referrer, + referrer_kind, + pattern, + internal, + conditions, + mode, + permissions, + ); + + match resolved_result { + Ok(Some(resolved)) => return Ok(Some(resolved)), + Ok(None) => { + last_error = None; + continue; + } + Err(e) => { + let err_string = e.to_string(); + last_error = Some(e); + if err_string.starts_with("[ERR_INVALID_PACKAGE_TARGET]") { + continue; + } + return Err(last_error.unwrap()); + } + } + } + if last_error.is_none() { + return Ok(None); + } + return Err(last_error.unwrap()); + } else if let Some(target_obj) = target.as_object() { + for key in target_obj.keys() { + // TODO(bartlomieju): verify that keys are not numeric + // return Err(errors::err_invalid_package_config( + // to_file_path_string(package_json_url), + // Some(base.as_str().to_string()), + // Some("\"exports\" cannot contain numeric property keys.".to_string()), + // )); + + if key == "default" + || conditions.contains(&key.as_str()) + || mode.is_types() && key.as_str() == "types" + { + let condition_target = target_obj.get(key).unwrap().to_owned(); + + let resolved = self.resolve_package_target( + package_json_path, + condition_target, + subpath.clone(), + package_subpath.clone(), + referrer, + referrer_kind, + pattern, + internal, + conditions, + mode, + permissions, + )?; + match resolved { + Some(resolved) => return Ok(Some(resolved)), + None => { + continue; + } + } + } + } + } else if target.is_null() { + return Ok(None); + } + + Err(throw_invalid_package_target( + package_subpath, + target.to_string(), + package_json_path, + internal, + referrer, + )) + } + + #[allow(clippy::too_many_arguments)] + pub fn package_exports_resolve( + &self, + package_json_path: &Path, + package_subpath: String, + package_exports: &Map, + referrer: &ModuleSpecifier, + referrer_kind: NodeModuleKind, + conditions: &[&str], + mode: NodeResolutionMode, + permissions: &mut dyn NodePermissions, + ) -> Result { + if package_exports.contains_key(&package_subpath) + && package_subpath.find('*').is_none() + && !package_subpath.ends_with('/') + { + let target = package_exports.get(&package_subpath).unwrap().to_owned(); + let resolved = self.resolve_package_target( + package_json_path, + target, + "".to_string(), + package_subpath.to_string(), + referrer, + referrer_kind, + false, + false, + conditions, + mode, + permissions, + )?; + if resolved.is_none() { + return Err(throw_exports_not_found( + package_subpath, + package_json_path, + referrer, + )); + } + return Ok(resolved.unwrap()); + } + + let mut best_match = ""; + let mut best_match_subpath = None; + for key in package_exports.keys() { + let pattern_index = key.find('*'); + if let Some(pattern_index) = pattern_index { + let key_sub = &key[0..pattern_index]; + if package_subpath.starts_with(key_sub) { + // When this reaches EOL, this can throw at the top of the whole function: + // + // if (StringPrototypeEndsWith(packageSubpath, '/')) + // throwInvalidSubpath(packageSubpath) + // + // To match "imports" and the spec. + if package_subpath.ends_with('/') { + // TODO(bartlomieju): + // emitTrailingSlashPatternDeprecation(); + } + let pattern_trailer = &key[pattern_index + 1..]; + if package_subpath.len() > key.len() + && package_subpath.ends_with(&pattern_trailer) + && pattern_key_compare(best_match, key) == 1 + && key.rfind('*') == Some(pattern_index) + { + best_match = key; + best_match_subpath = Some( + package_subpath[pattern_index + ..(package_subpath.len() - pattern_trailer.len())] + .to_string(), + ); + } + } + } + } + + if !best_match.is_empty() { + let target = package_exports.get(best_match).unwrap().to_owned(); + let maybe_resolved = self.resolve_package_target( + package_json_path, + target, + best_match_subpath.unwrap(), + best_match.to_string(), + referrer, + referrer_kind, + true, + false, + conditions, + mode, + permissions, + )?; + if let Some(resolved) = maybe_resolved { + return Ok(resolved); + } else { + return Err(throw_exports_not_found( + package_subpath, + package_json_path, + referrer, + )); + } + } + + Err(throw_exports_not_found( + package_subpath, + package_json_path, + referrer, + )) + } + + pub(super) fn package_resolve( + &self, + specifier: &str, + referrer: &ModuleSpecifier, + referrer_kind: NodeModuleKind, + conditions: &[&str], + mode: NodeResolutionMode, + permissions: &mut dyn NodePermissions, + ) -> Result, AnyError> { + let (package_name, package_subpath, _is_scoped) = + parse_package_name(specifier, referrer)?; + + // ResolveSelf + let package_config = + self.get_package_scope_config(referrer, permissions)?; + if package_config.exists + && package_config.name.as_ref() == Some(&package_name) + { + if let Some(exports) = &package_config.exports { + return self + .package_exports_resolve( + &package_config.path, + package_subpath, + exports, + referrer, + referrer_kind, + conditions, + mode, + permissions, + ) + .map(Some); + } + } + + let package_dir_path = self + .npm_resolver + .resolve_package_folder_from_package(&package_name, referrer, mode)?; + let package_json_path = package_dir_path.join("package.json"); + + // todo: error with this instead when can't find package + // Err(errors::err_module_not_found( + // &package_json_url + // .join(".") + // .unwrap() + // .to_file_path() + // .unwrap() + // .display() + // .to_string(), + // &to_file_path_string(referrer), + // "package", + // )) + + // Package match. + let package_json = + self.load_package_json(permissions, package_json_path)?; + if let Some(exports) = &package_json.exports { + return self + .package_exports_resolve( + &package_json.path, + package_subpath, + exports, + referrer, + referrer_kind, + conditions, + mode, + permissions, + ) + .map(Some); + } + if package_subpath == "." { + return self.legacy_main_resolve(&package_json, referrer_kind, mode); + } + + let file_path = package_json.path.parent().unwrap().join(&package_subpath); + + if mode.is_types() { + let maybe_declaration_path = + self.path_to_declaration_path(file_path, referrer_kind); + Ok(maybe_declaration_path) + } else { + Ok(Some(file_path)) + } + } + + pub(super) fn get_package_scope_config( + &self, + referrer: &ModuleSpecifier, + permissions: &mut dyn NodePermissions, + ) -> Result { + let root_folder = self + .npm_resolver + .resolve_package_folder_from_path(&referrer.to_file_path().unwrap())?; + let package_json_path = root_folder.join("package.json"); + self.load_package_json(permissions, package_json_path) + } + + pub(super) fn get_closest_package_json( + &self, + url: &ModuleSpecifier, + permissions: &mut dyn NodePermissions, + ) -> Result { + let package_json_path = self.get_closest_package_json_path(url)?; + self.load_package_json(permissions, package_json_path) + } + + fn get_closest_package_json_path( + &self, + url: &ModuleSpecifier, + ) -> Result { + let file_path = url.to_file_path().unwrap(); + let mut current_dir = file_path.parent().unwrap(); + let package_json_path = current_dir.join("package.json"); + if self.fs.exists(&package_json_path) { + return Ok(package_json_path); + } + let root_pkg_folder = self + .npm_resolver + .resolve_package_folder_from_path(&url.to_file_path().unwrap())?; + while current_dir.starts_with(&root_pkg_folder) { + current_dir = current_dir.parent().unwrap(); + let package_json_path = current_dir.join("package.json"); + if self.fs.exists(&package_json_path) { + return Ok(package_json_path); + } + } + + bail!("did not find package.json in {}", root_pkg_folder.display()) + } + + pub(super) fn load_package_json( + &self, + permissions: &mut dyn NodePermissions, + package_json_path: PathBuf, + ) -> Result { + PackageJson::load( + &*self.fs, + &*self.npm_resolver, + permissions, + package_json_path, + ) + } + + pub(super) fn legacy_main_resolve( + &self, + package_json: &PackageJson, + referrer_kind: NodeModuleKind, + mode: NodeResolutionMode, + ) -> Result, AnyError> { + let maybe_main = if mode.is_types() { + match package_json.types.as_ref() { + Some(types) => Some(types), + None => { + // fallback to checking the main entrypoint for + // a corresponding declaration file + if let Some(main) = package_json.main(referrer_kind) { + let main = package_json.path.parent().unwrap().join(main).clean(); + if let Some(path) = + self.path_to_declaration_path(main, referrer_kind) + { + return Ok(Some(path)); + } + } + None + } + } + } else { + package_json.main(referrer_kind) + }; + + if let Some(main) = maybe_main { + let guess = package_json.path.parent().unwrap().join(main).clean(); + if self.fs.is_file(&guess) { + return Ok(Some(guess)); + } + + // todo(dsherret): investigate exactly how node and typescript handles this + let endings = if mode.is_types() { + match referrer_kind { + NodeModuleKind::Cjs => { + vec![".d.ts", ".d.cts", "/index.d.ts", "/index.d.cts"] + } + NodeModuleKind::Esm => vec![ + ".d.ts", + ".d.mts", + "/index.d.ts", + "/index.d.mts", + ".d.cts", + "/index.d.cts", + ], + } + } else { + vec![".js", "/index.js"] + }; + for ending in endings { + let guess = package_json + .path + .parent() + .unwrap() + .join(format!("{main}{ending}")) + .clean(); + if self.fs.is_file(&guess) { + // TODO(bartlomieju): emitLegacyIndexDeprecation() + return Ok(Some(guess)); + } + } + } + + let index_file_names = if mode.is_types() { + // todo(dsherret): investigate exactly how typescript does this + match referrer_kind { + NodeModuleKind::Cjs => vec!["index.d.ts", "index.d.cts"], + NodeModuleKind::Esm => vec!["index.d.ts", "index.d.mts", "index.d.cts"], + } + } else { + vec!["index.js"] + }; + for index_file_name in index_file_names { + let guess = package_json + .path + .parent() + .unwrap() + .join(index_file_name) + .clean(); + if self.fs.is_file(&guess) { + // TODO(bartlomieju): emitLegacyIndexDeprecation() + return Ok(Some(guess)); + } + } + + Ok(None) + } +} + +fn resolve_bin_entry_value<'a>( + pkg_nv: &NpmPackageNv, + bin_name: Option<&str>, + bin: &'a Value, +) -> Result<&'a str, AnyError> { + let bin_entry = match bin { + Value::String(_) => { + if bin_name.is_some() && bin_name.unwrap() != pkg_nv.name { + None + } else { + Some(bin) + } + } + Value::Object(o) => { + if let Some(bin_name) = bin_name { + o.get(bin_name) + } else if o.len() == 1 || o.len() > 1 && o.values().all(|v| v == o.values().next().unwrap()) { + o.values().next() + } else { + o.get(&pkg_nv.name) + } + }, + _ => bail!("package '{}' did not have a bin property with a string or object value in its package.json", pkg_nv), + }; + let bin_entry = match bin_entry { + Some(e) => e, + None => { + let keys = bin + .as_object() + .map(|o| { + o.keys() + .map(|k| format!(" * npm:{pkg_nv}/{k}")) + .collect::>() + }) + .unwrap_or_default(); + bail!( + "package '{}' did not have a bin entry for '{}' in its package.json{}", + pkg_nv, + bin_name.unwrap_or(&pkg_nv.name), + if keys.is_empty() { + "".to_string() + } else { + format!("\n\nPossibilities:\n{}", keys.join("\n")) + } + ) + } + }; + match bin_entry { + Value::String(s) => Ok(s), + _ => bail!( + "package '{}' had a non-string sub property of bin in its package.json", + pkg_nv, + ), + } +} + +fn to_file_path(url: &ModuleSpecifier) -> PathBuf { + url + .to_file_path() + .unwrap_or_else(|_| panic!("Provided URL was not file:// URL: {url}")) +} + +fn to_file_path_string(url: &ModuleSpecifier) -> String { + to_file_path(url).display().to_string() +} + +fn should_be_treated_as_relative_or_absolute_path(specifier: &str) -> bool { + if specifier.is_empty() { + return false; + } + + if specifier.starts_with('/') { + return true; + } + + is_relative_specifier(specifier) +} + +// TODO(ry) We very likely have this utility function elsewhere in Deno. +fn is_relative_specifier(specifier: &str) -> bool { + let specifier_len = specifier.len(); + let specifier_chars: Vec<_> = specifier.chars().collect(); + + if !specifier_chars.is_empty() && specifier_chars[0] == '.' { + if specifier_len == 1 || specifier_chars[1] == '/' { + return true; + } + if specifier_chars[1] == '.' + && (specifier_len == 2 || specifier_chars[2] == '/') + { + return true; + } + } + false } /// Alternate `PathBuf::with_extension` that will handle known extensions /// more intelligently. -pub fn with_known_extension(path: &Path, ext: &str) -> PathBuf { +fn with_known_extension(path: &Path, ext: &str) -> PathBuf { const NON_DECL_EXTS: &[&str] = &["cjs", "js", "json", "jsx", "mjs", "tsx"]; const DECL_EXTS: &[&str] = &["cts", "mts", "ts"]; @@ -142,145 +1356,6 @@ fn throw_import_not_defined( ) } -fn pattern_key_compare(a: &str, b: &str) -> i32 { - let a_pattern_index = a.find('*'); - let b_pattern_index = b.find('*'); - - let base_len_a = if let Some(index) = a_pattern_index { - index + 1 - } else { - a.len() - }; - let base_len_b = if let Some(index) = b_pattern_index { - index + 1 - } else { - b.len() - }; - - if base_len_a > base_len_b { - return -1; - } - - if base_len_b > base_len_a { - return 1; - } - - if a_pattern_index.is_none() { - return 1; - } - - if b_pattern_index.is_none() { - return -1; - } - - if a.len() > b.len() { - return -1; - } - - if b.len() > a.len() { - return 1; - } - - 0 -} - -pub fn package_imports_resolve( - name: &str, - referrer: &ModuleSpecifier, - referrer_kind: NodeModuleKind, - conditions: &[&str], - mode: NodeResolutionMode, - npm_resolver: &dyn NpmResolver, - permissions: &mut dyn NodePermissions, -) -> Result { - if name == "#" || name.starts_with("#/") || name.ends_with('/') { - let reason = "is not a valid internal imports specifier name"; - return Err(errors::err_invalid_module_specifier( - name, - reason, - Some(to_specifier_display_string(referrer)), - )); - } - - let package_config = - get_package_scope_config::(referrer, npm_resolver, permissions)?; - let mut package_json_path = None; - if package_config.exists { - package_json_path = Some(package_config.path.clone()); - if let Some(imports) = &package_config.imports { - if imports.contains_key(name) && !name.contains('*') { - let maybe_resolved = resolve_package_target::( - package_json_path.as_ref().unwrap(), - imports.get(name).unwrap().to_owned(), - "".to_string(), - name.to_string(), - referrer, - referrer_kind, - false, - true, - conditions, - mode, - npm_resolver, - permissions, - )?; - if let Some(resolved) = maybe_resolved { - return Ok(resolved); - } - } else { - let mut best_match = ""; - let mut best_match_subpath = None; - for key in imports.keys() { - let pattern_index = key.find('*'); - if let Some(pattern_index) = pattern_index { - let key_sub = &key[0..=pattern_index]; - if name.starts_with(key_sub) { - let pattern_trailer = &key[pattern_index + 1..]; - if name.len() > key.len() - && name.ends_with(&pattern_trailer) - && pattern_key_compare(best_match, key) == 1 - && key.rfind('*') == Some(pattern_index) - { - best_match = key; - best_match_subpath = Some( - name[pattern_index..=(name.len() - pattern_trailer.len())] - .to_string(), - ); - } - } - } - } - - if !best_match.is_empty() { - let target = imports.get(best_match).unwrap().to_owned(); - let maybe_resolved = resolve_package_target::( - package_json_path.as_ref().unwrap(), - target, - best_match_subpath.unwrap(), - best_match.to_string(), - referrer, - referrer_kind, - true, - true, - conditions, - mode, - npm_resolver, - permissions, - )?; - if let Some(resolved) = maybe_resolved { - return Ok(resolved); - } - } - } - } - } - - Err(throw_import_not_defined( - name, - package_json_path.as_deref(), - referrer, - )) -} - fn throw_invalid_package_target( subpath: String, target: String, @@ -316,245 +1391,6 @@ fn throw_invalid_subpath( ) } -#[allow(clippy::too_many_arguments)] -fn resolve_package_target_string( - target: String, - subpath: String, - match_: String, - package_json_path: &Path, - referrer: &ModuleSpecifier, - referrer_kind: NodeModuleKind, - pattern: bool, - internal: bool, - conditions: &[&str], - mode: NodeResolutionMode, - npm_resolver: &dyn NpmResolver, - permissions: &mut dyn NodePermissions, -) -> Result { - if !subpath.is_empty() && !pattern && !target.ends_with('/') { - return Err(throw_invalid_package_target( - match_, - target, - package_json_path, - internal, - referrer, - )); - } - let invalid_segment_re = - lazy_regex::regex!(r"(^|\\|/)(\.\.?|node_modules)(\\|/|$)"); - let pattern_re = lazy_regex::regex!(r"\*"); - if !target.starts_with("./") { - if internal && !target.starts_with("../") && !target.starts_with('/') { - let is_url = Url::parse(&target).is_ok(); - if !is_url { - let export_target = if pattern { - pattern_re - .replace(&target, |_caps: ®ex::Captures| subpath.clone()) - .to_string() - } else { - format!("{target}{subpath}") - }; - let package_json_url = - ModuleSpecifier::from_file_path(package_json_path).unwrap(); - return match package_resolve::( - &export_target, - &package_json_url, - referrer_kind, - conditions, - mode, - npm_resolver, - permissions, - ) { - Ok(Some(path)) => Ok(path), - Ok(None) => Err(generic_error("not found")), - Err(err) => Err(err), - }; - } - } - return Err(throw_invalid_package_target( - match_, - target, - package_json_path, - internal, - referrer, - )); - } - if invalid_segment_re.is_match(&target[2..]) { - return Err(throw_invalid_package_target( - match_, - target, - package_json_path, - internal, - referrer, - )); - } - let package_path = package_json_path.parent().unwrap(); - let resolved_path = package_path.join(&target).clean(); - if !resolved_path.starts_with(package_path) { - return Err(throw_invalid_package_target( - match_, - target, - package_json_path, - internal, - referrer, - )); - } - if subpath.is_empty() { - return Ok(resolved_path); - } - if invalid_segment_re.is_match(&subpath) { - let request = if pattern { - match_.replace('*', &subpath) - } else { - format!("{match_}{subpath}") - }; - return Err(throw_invalid_subpath( - request, - package_json_path, - internal, - referrer, - )); - } - if pattern { - let resolved_path_str = resolved_path.to_string_lossy(); - let replaced = pattern_re - .replace(&resolved_path_str, |_caps: ®ex::Captures| { - subpath.clone() - }); - return Ok(PathBuf::from(replaced.to_string())); - } - Ok(resolved_path.join(&subpath).clean()) -} - -#[allow(clippy::too_many_arguments)] -fn resolve_package_target( - package_json_path: &Path, - target: Value, - subpath: String, - package_subpath: String, - referrer: &ModuleSpecifier, - referrer_kind: NodeModuleKind, - pattern: bool, - internal: bool, - conditions: &[&str], - mode: NodeResolutionMode, - npm_resolver: &dyn NpmResolver, - permissions: &mut dyn NodePermissions, -) -> Result, AnyError> { - if let Some(target) = target.as_str() { - return resolve_package_target_string::( - target.to_string(), - subpath, - package_subpath, - package_json_path, - referrer, - referrer_kind, - pattern, - internal, - conditions, - mode, - npm_resolver, - permissions, - ) - .map(|path| { - if mode.is_types() { - path_to_declaration_path::(path, referrer_kind) - } else { - Some(path) - } - }); - } else if let Some(target_arr) = target.as_array() { - if target_arr.is_empty() { - return Ok(None); - } - - let mut last_error = None; - for target_item in target_arr { - let resolved_result = resolve_package_target::( - package_json_path, - target_item.to_owned(), - subpath.clone(), - package_subpath.clone(), - referrer, - referrer_kind, - pattern, - internal, - conditions, - mode, - npm_resolver, - permissions, - ); - - match resolved_result { - Ok(Some(resolved)) => return Ok(Some(resolved)), - Ok(None) => { - last_error = None; - continue; - } - Err(e) => { - let err_string = e.to_string(); - last_error = Some(e); - if err_string.starts_with("[ERR_INVALID_PACKAGE_TARGET]") { - continue; - } - return Err(last_error.unwrap()); - } - } - } - if last_error.is_none() { - return Ok(None); - } - return Err(last_error.unwrap()); - } else if let Some(target_obj) = target.as_object() { - for key in target_obj.keys() { - // TODO(bartlomieju): verify that keys are not numeric - // return Err(errors::err_invalid_package_config( - // to_file_path_string(package_json_url), - // Some(base.as_str().to_string()), - // Some("\"exports\" cannot contain numeric property keys.".to_string()), - // )); - - if key == "default" - || conditions.contains(&key.as_str()) - || mode.is_types() && key.as_str() == "types" - { - let condition_target = target_obj.get(key).unwrap().to_owned(); - - let resolved = resolve_package_target::( - package_json_path, - condition_target, - subpath.clone(), - package_subpath.clone(), - referrer, - referrer_kind, - pattern, - internal, - conditions, - mode, - npm_resolver, - permissions, - )?; - match resolved { - Some(resolved) => return Ok(Some(resolved)), - None => { - continue; - } - } - } - } - } else if target.is_null() { - return Ok(None); - } - - Err(throw_invalid_package_target( - package_subpath, - target.to_string(), - package_json_path, - internal, - referrer, - )) -} - fn throw_exports_not_found( subpath: String, package_json_path: &Path, @@ -567,115 +1403,6 @@ fn throw_exports_not_found( ) } -#[allow(clippy::too_many_arguments)] -pub fn package_exports_resolve( - package_json_path: &Path, - package_subpath: String, - package_exports: &Map, - referrer: &ModuleSpecifier, - referrer_kind: NodeModuleKind, - conditions: &[&str], - mode: NodeResolutionMode, - npm_resolver: &dyn NpmResolver, - permissions: &mut dyn NodePermissions, -) -> Result { - if package_exports.contains_key(&package_subpath) - && package_subpath.find('*').is_none() - && !package_subpath.ends_with('/') - { - let target = package_exports.get(&package_subpath).unwrap().to_owned(); - let resolved = resolve_package_target::( - package_json_path, - target, - "".to_string(), - package_subpath.to_string(), - referrer, - referrer_kind, - false, - false, - conditions, - mode, - npm_resolver, - permissions, - )?; - if resolved.is_none() { - return Err(throw_exports_not_found( - package_subpath, - package_json_path, - referrer, - )); - } - return Ok(resolved.unwrap()); - } - - let mut best_match = ""; - let mut best_match_subpath = None; - for key in package_exports.keys() { - let pattern_index = key.find('*'); - if let Some(pattern_index) = pattern_index { - let key_sub = &key[0..pattern_index]; - if package_subpath.starts_with(key_sub) { - // When this reaches EOL, this can throw at the top of the whole function: - // - // if (StringPrototypeEndsWith(packageSubpath, '/')) - // throwInvalidSubpath(packageSubpath) - // - // To match "imports" and the spec. - if package_subpath.ends_with('/') { - // TODO(bartlomieju): - // emitTrailingSlashPatternDeprecation(); - } - let pattern_trailer = &key[pattern_index + 1..]; - if package_subpath.len() > key.len() - && package_subpath.ends_with(&pattern_trailer) - && pattern_key_compare(best_match, key) == 1 - && key.rfind('*') == Some(pattern_index) - { - best_match = key; - best_match_subpath = Some( - package_subpath - [pattern_index..(package_subpath.len() - pattern_trailer.len())] - .to_string(), - ); - } - } - } - } - - if !best_match.is_empty() { - let target = package_exports.get(best_match).unwrap().to_owned(); - let maybe_resolved = resolve_package_target::( - package_json_path, - target, - best_match_subpath.unwrap(), - best_match.to_string(), - referrer, - referrer_kind, - true, - false, - conditions, - mode, - npm_resolver, - permissions, - )?; - if let Some(resolved) = maybe_resolved { - return Ok(resolved); - } else { - return Err(throw_exports_not_found( - package_subpath, - package_json_path, - referrer, - )); - } - } - - Err(throw_exports_not_found( - package_subpath, - package_json_path, - referrer, - )) -} - fn parse_package_name( specifier: &str, referrer: &ModuleSpecifier, @@ -727,229 +1454,153 @@ fn parse_package_name( Ok((package_name, package_subpath, is_scoped)) } -pub fn package_resolve( - specifier: &str, - referrer: &ModuleSpecifier, - referrer_kind: NodeModuleKind, - conditions: &[&str], - mode: NodeResolutionMode, - npm_resolver: &dyn NpmResolver, - permissions: &mut dyn NodePermissions, -) -> Result, AnyError> { - let (package_name, package_subpath, _is_scoped) = - parse_package_name(specifier, referrer)?; +fn pattern_key_compare(a: &str, b: &str) -> i32 { + let a_pattern_index = a.find('*'); + let b_pattern_index = b.find('*'); - // ResolveSelf - let package_config = - get_package_scope_config::(referrer, npm_resolver, permissions)?; - if package_config.exists - && package_config.name.as_ref() == Some(&package_name) - { - if let Some(exports) = &package_config.exports { - return package_exports_resolve::( - &package_config.path, - package_subpath, - exports, - referrer, - referrer_kind, - conditions, - mode, - npm_resolver, - permissions, - ) - .map(Some); - } - } - - let package_dir_path = npm_resolver.resolve_package_folder_from_package( - &package_name, - referrer, - mode, - )?; - let package_json_path = package_dir_path.join("package.json"); - - // todo: error with this instead when can't find package - // Err(errors::err_module_not_found( - // &package_json_url - // .join(".") - // .unwrap() - // .to_file_path() - // .unwrap() - // .display() - // .to_string(), - // &to_file_path_string(referrer), - // "package", - // )) - - // Package match. - let package_json = - PackageJson::load::(npm_resolver, permissions, package_json_path)?; - if let Some(exports) = &package_json.exports { - return package_exports_resolve::( - &package_json.path, - package_subpath, - exports, - referrer, - referrer_kind, - conditions, - mode, - npm_resolver, - permissions, - ) - .map(Some); - } - if package_subpath == "." { - return legacy_main_resolve::(&package_json, referrer_kind, mode); - } - - let file_path = package_json.path.parent().unwrap().join(&package_subpath); - - if mode.is_types() { - let maybe_declaration_path = - path_to_declaration_path::(file_path, referrer_kind); - Ok(maybe_declaration_path) + let base_len_a = if let Some(index) = a_pattern_index { + index + 1 } else { - Ok(Some(file_path)) - } -} - -pub fn get_package_scope_config( - referrer: &ModuleSpecifier, - npm_resolver: &dyn NpmResolver, - permissions: &mut dyn NodePermissions, -) -> Result { - let root_folder = npm_resolver - .resolve_package_folder_from_path(&referrer.to_file_path().unwrap())?; - let package_json_path = root_folder.join("package.json"); - PackageJson::load::(npm_resolver, permissions, package_json_path) -} - -pub fn get_closest_package_json( - url: &ModuleSpecifier, - npm_resolver: &dyn NpmResolver, - permissions: &mut dyn NodePermissions, -) -> Result { - let package_json_path = - get_closest_package_json_path::(url, npm_resolver)?; - PackageJson::load::(npm_resolver, permissions, package_json_path) -} - -fn get_closest_package_json_path( - url: &ModuleSpecifier, - npm_resolver: &dyn NpmResolver, -) -> Result { - let file_path = url.to_file_path().unwrap(); - let mut current_dir = file_path.parent().unwrap(); - let package_json_path = current_dir.join("package.json"); - if Fs::exists(&package_json_path) { - return Ok(package_json_path); - } - let root_pkg_folder = npm_resolver - .resolve_package_folder_from_path(&url.to_file_path().unwrap())?; - while current_dir.starts_with(&root_pkg_folder) { - current_dir = current_dir.parent().unwrap(); - let package_json_path = current_dir.join("package.json"); - if Fs::exists(&package_json_path) { - return Ok(package_json_path); - } - } - - bail!("did not find package.json in {}", root_pkg_folder.display()) -} - -pub fn legacy_main_resolve( - package_json: &PackageJson, - referrer_kind: NodeModuleKind, - mode: NodeResolutionMode, -) -> Result, AnyError> { - let maybe_main = if mode.is_types() { - match package_json.types.as_ref() { - Some(types) => Some(types), - None => { - // fallback to checking the main entrypoint for - // a corresponding declaration file - if let Some(main) = package_json.main(referrer_kind) { - let main = package_json.path.parent().unwrap().join(main).clean(); - if let Some(path) = - path_to_declaration_path::(main, referrer_kind) - { - return Ok(Some(path)); - } - } - None - } - } + a.len() + }; + let base_len_b = if let Some(index) = b_pattern_index { + index + 1 } else { - package_json.main(referrer_kind) + b.len() }; - if let Some(main) = maybe_main { - let guess = package_json.path.parent().unwrap().join(main).clean(); - if Fs::is_file(&guess) { - return Ok(Some(guess)); - } - - // todo(dsherret): investigate exactly how node and typescript handles this - let endings = if mode.is_types() { - match referrer_kind { - NodeModuleKind::Cjs => { - vec![".d.ts", ".d.cts", "/index.d.ts", "/index.d.cts"] - } - NodeModuleKind::Esm => vec![ - ".d.ts", - ".d.mts", - "/index.d.ts", - "/index.d.mts", - ".d.cts", - "/index.d.cts", - ], - } - } else { - vec![".js", "/index.js"] - }; - for ending in endings { - let guess = package_json - .path - .parent() - .unwrap() - .join(format!("{main}{ending}")) - .clean(); - if Fs::is_file(&guess) { - // TODO(bartlomieju): emitLegacyIndexDeprecation() - return Ok(Some(guess)); - } - } + if base_len_a > base_len_b { + return -1; } - let index_file_names = if mode.is_types() { - // todo(dsherret): investigate exactly how typescript does this - match referrer_kind { - NodeModuleKind::Cjs => vec!["index.d.ts", "index.d.cts"], - NodeModuleKind::Esm => vec!["index.d.ts", "index.d.mts", "index.d.cts"], - } - } else { - vec!["index.js"] - }; - for index_file_name in index_file_names { - let guess = package_json - .path - .parent() - .unwrap() - .join(index_file_name) - .clean(); - if Fs::is_file(&guess) { - // TODO(bartlomieju): emitLegacyIndexDeprecation() - return Ok(Some(guess)); - } + if base_len_b > base_len_a { + return 1; } - Ok(None) + if a_pattern_index.is_none() { + return 1; + } + + if b_pattern_index.is_none() { + return -1; + } + + if a.len() > b.len() { + return -1; + } + + if b.len() > a.len() { + return 1; + } + + 0 } #[cfg(test)] mod tests { + use deno_core::serde_json::json; + use super::*; + #[test] + fn test_resolve_bin_entry_value() { + // should resolve the specified value + let value = json!({ + "bin1": "./value1", + "bin2": "./value2", + "test": "./value3", + }); + assert_eq!( + resolve_bin_entry_value( + &NpmPackageNv::from_str("test@1.1.1").unwrap(), + Some("bin1"), + &value + ) + .unwrap(), + "./value1" + ); + + // should resolve the value with the same name when not specified + assert_eq!( + resolve_bin_entry_value( + &NpmPackageNv::from_str("test@1.1.1").unwrap(), + None, + &value + ) + .unwrap(), + "./value3" + ); + + // should not resolve when specified value does not exist + assert_eq!( + resolve_bin_entry_value( + &NpmPackageNv::from_str("test@1.1.1").unwrap(), + Some("other"), + &value + ) + .err() + .unwrap() + .to_string(), + concat!( + "package 'test@1.1.1' did not have a bin entry for 'other' in its package.json\n", + "\n", + "Possibilities:\n", + " * npm:test@1.1.1/bin1\n", + " * npm:test@1.1.1/bin2\n", + " * npm:test@1.1.1/test" + ) + ); + + // should not resolve when default value can't be determined + assert_eq!( + resolve_bin_entry_value( + &NpmPackageNv::from_str("asdf@1.2.3").unwrap(), + None, + &value + ) + .err() + .unwrap() + .to_string(), + concat!( + "package 'asdf@1.2.3' did not have a bin entry for 'asdf' in its package.json\n", + "\n", + "Possibilities:\n", + " * npm:asdf@1.2.3/bin1\n", + " * npm:asdf@1.2.3/bin2\n", + " * npm:asdf@1.2.3/test" + ) + ); + + // should resolve since all the values are the same + let value = json!({ + "bin1": "./value", + "bin2": "./value", + }); + assert_eq!( + resolve_bin_entry_value( + &NpmPackageNv::from_str("test@1.2.3").unwrap(), + None, + &value + ) + .unwrap(), + "./value" + ); + + // should not resolve when specified and is a string + let value = json!("./value"); + assert_eq!( + resolve_bin_entry_value( + &NpmPackageNv::from_str("test@1.2.3").unwrap(), + Some("path"), + &value + ) + .err() + .unwrap() + .to_string(), + "package 'test@1.2.3' did not have a bin entry for 'path' in its package.json" + ); + } + #[test] fn test_parse_package_name() { let dummy_referrer = Url::parse("http://example.com").unwrap(); diff --git a/ext/node/resolver.rs b/ext/node/resolver.rs deleted file mode 100644 index 41e1cf4d4d..0000000000 --- a/ext/node/resolver.rs +++ /dev/null @@ -1,686 +0,0 @@ -// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. - -use std::path::Path; -use std::path::PathBuf; - -use deno_core::anyhow::bail; -use deno_core::anyhow::Context; -use deno_core::error::generic_error; -use deno_core::error::AnyError; -use deno_core::serde_json::Value; -use deno_core::url::Url; -use deno_core::ModuleSpecifier; -use deno_media_type::MediaType; -use deno_semver::npm::NpmPackageNv; -use deno_semver::npm::NpmPackageNvReference; -use deno_semver::npm::NpmPackageReqReference; - -use crate::errors; -use crate::get_closest_package_json; -use crate::legacy_main_resolve; -use crate::package_exports_resolve; -use crate::package_imports_resolve; -use crate::package_resolve; -use crate::path_to_declaration_path; -use crate::AllowAllNodePermissions; -use crate::NodeFs; -use crate::NodeModuleKind; -use crate::NodePermissions; -use crate::NodeResolutionMode; -use crate::NpmResolver; -use crate::PackageJson; -use crate::DEFAULT_CONDITIONS; - -#[derive(Debug)] -pub enum NodeResolution { - Esm(ModuleSpecifier), - CommonJs(ModuleSpecifier), - BuiltIn(String), -} - -impl NodeResolution { - pub fn into_url(self) -> ModuleSpecifier { - match self { - Self::Esm(u) => u, - Self::CommonJs(u) => u, - Self::BuiltIn(specifier) => { - if specifier.starts_with("node:") { - ModuleSpecifier::parse(&specifier).unwrap() - } else { - ModuleSpecifier::parse(&format!("node:{specifier}")).unwrap() - } - } - } - } - - pub fn into_specifier_and_media_type( - resolution: Option, - ) -> (ModuleSpecifier, MediaType) { - match resolution { - Some(NodeResolution::CommonJs(specifier)) => { - let media_type = MediaType::from_specifier(&specifier); - ( - specifier, - match media_type { - MediaType::JavaScript | MediaType::Jsx => MediaType::Cjs, - MediaType::TypeScript | MediaType::Tsx => MediaType::Cts, - MediaType::Dts => MediaType::Dcts, - _ => media_type, - }, - ) - } - Some(NodeResolution::Esm(specifier)) => { - let media_type = MediaType::from_specifier(&specifier); - ( - specifier, - match media_type { - MediaType::JavaScript | MediaType::Jsx => MediaType::Mjs, - MediaType::TypeScript | MediaType::Tsx => MediaType::Mts, - MediaType::Dts => MediaType::Dmts, - _ => media_type, - }, - ) - } - Some(resolution) => (resolution.into_url(), MediaType::Dts), - None => ( - ModuleSpecifier::parse("internal:///missing_dependency.d.ts").unwrap(), - MediaType::Dts, - ), - } - } -} - -#[derive(Debug)] -pub struct NodeResolver { - npm_resolver: TRequireNpmResolver, -} - -impl NodeResolver { - pub fn new(require_npm_resolver: TRequireNpmResolver) -> Self { - Self { - npm_resolver: require_npm_resolver, - } - } - - pub fn in_npm_package(&self, specifier: &ModuleSpecifier) -> bool { - self.npm_resolver.in_npm_package(specifier) - } - - /// This function is an implementation of `defaultResolve` in - /// `lib/internal/modules/esm/resolve.js` from Node. - pub fn resolve( - &self, - specifier: &str, - referrer: &ModuleSpecifier, - mode: NodeResolutionMode, - permissions: &mut dyn NodePermissions, - ) -> Result, AnyError> { - // Note: if we are here, then the referrer is an esm module - // TODO(bartlomieju): skipped "policy" part as we don't plan to support it - - if crate::is_builtin_node_module(specifier) { - return Ok(Some(NodeResolution::BuiltIn(specifier.to_string()))); - } - - if let Ok(url) = Url::parse(specifier) { - if url.scheme() == "data" { - return Ok(Some(NodeResolution::Esm(url))); - } - - let protocol = url.scheme(); - - if protocol == "node" { - let split_specifier = url.as_str().split(':'); - let specifier = split_specifier.skip(1).collect::(); - - if crate::is_builtin_node_module(&specifier) { - return Ok(Some(NodeResolution::BuiltIn(specifier))); - } - } - - if protocol != "file" && protocol != "data" { - return Err(errors::err_unsupported_esm_url_scheme(&url)); - } - - // todo(dsherret): this seems wrong - if referrer.scheme() == "data" { - let url = referrer.join(specifier).map_err(AnyError::from)?; - return Ok(Some(NodeResolution::Esm(url))); - } - } - - let url = self.module_resolve::( - specifier, - referrer, - DEFAULT_CONDITIONS, - mode, - permissions, - )?; - let url = match url { - Some(url) => url, - None => return Ok(None), - }; - let url = match mode { - NodeResolutionMode::Execution => url, - NodeResolutionMode::Types => { - let path = url.to_file_path().unwrap(); - // todo(16370): the module kind is not correct here. I think we need - // typescript to tell us if the referrer is esm or cjs - let path = - match path_to_declaration_path::(path, NodeModuleKind::Esm) { - Some(path) => path, - None => return Ok(None), - }; - ModuleSpecifier::from_file_path(path).unwrap() - } - }; - - let resolve_response = self.url_to_node_resolution::(url)?; - // TODO(bartlomieju): skipped checking errors for commonJS resolution and - // "preserveSymlinksMain"/"preserveSymlinks" options. - Ok(Some(resolve_response)) - } - - fn module_resolve( - &self, - specifier: &str, - referrer: &ModuleSpecifier, - conditions: &[&str], - mode: NodeResolutionMode, - permissions: &mut dyn NodePermissions, - ) -> Result, AnyError> { - // note: if we're here, the referrer is an esm module - let url = if should_be_treated_as_relative_or_absolute_path(specifier) { - let resolved_specifier = referrer.join(specifier)?; - if mode.is_types() { - let file_path = to_file_path(&resolved_specifier); - // todo(dsherret): the node module kind is not correct and we - // should use the value provided by typescript instead - let declaration_path = - path_to_declaration_path::(file_path, NodeModuleKind::Esm); - declaration_path.map(|declaration_path| { - ModuleSpecifier::from_file_path(declaration_path).unwrap() - }) - } else { - Some(resolved_specifier) - } - } else if specifier.starts_with('#') { - Some( - package_imports_resolve::( - specifier, - referrer, - NodeModuleKind::Esm, - conditions, - mode, - &self.npm_resolver, - permissions, - ) - .map(|p| ModuleSpecifier::from_file_path(p).unwrap())?, - ) - } else if let Ok(resolved) = Url::parse(specifier) { - Some(resolved) - } else { - package_resolve::( - specifier, - referrer, - NodeModuleKind::Esm, - conditions, - mode, - &self.npm_resolver, - permissions, - )? - .map(|p| ModuleSpecifier::from_file_path(p).unwrap()) - }; - Ok(match url { - Some(url) => Some(finalize_resolution::(url, referrer)?), - None => None, - }) - } - - pub fn resolve_npm_req_reference( - &self, - reference: &NpmPackageReqReference, - mode: NodeResolutionMode, - permissions: &mut dyn NodePermissions, - ) -> Result, AnyError> { - let reference = self - .npm_resolver - .resolve_nv_ref_from_pkg_req_ref(reference)?; - self.resolve_npm_reference::(&reference, mode, permissions) - } - - pub fn resolve_npm_reference( - &self, - reference: &NpmPackageNvReference, - mode: NodeResolutionMode, - permissions: &mut dyn NodePermissions, - ) -> Result, AnyError> { - let package_folder = self - .npm_resolver - .resolve_package_folder_from_deno_module(&reference.nv)?; - let node_module_kind = NodeModuleKind::Esm; - let maybe_resolved_path = package_config_resolve::( - &reference - .sub_path - .as_ref() - .map(|s| format!("./{s}")) - .unwrap_or_else(|| ".".to_string()), - &package_folder, - node_module_kind, - DEFAULT_CONDITIONS, - mode, - &self.npm_resolver, - permissions, - ) - .with_context(|| { - format!("Error resolving package config for '{reference}'") - })?; - let resolved_path = match maybe_resolved_path { - Some(resolved_path) => resolved_path, - None => return Ok(None), - }; - let resolved_path = match mode { - NodeResolutionMode::Execution => resolved_path, - NodeResolutionMode::Types => { - match path_to_declaration_path::(resolved_path, node_module_kind) { - Some(path) => path, - None => return Ok(None), - } - } - }; - let url = ModuleSpecifier::from_file_path(resolved_path).unwrap(); - let resolve_response = self.url_to_node_resolution::(url)?; - // TODO(bartlomieju): skipped checking errors for commonJS resolution and - // "preserveSymlinksMain"/"preserveSymlinks" options. - Ok(Some(resolve_response)) - } - - pub fn resolve_binary_commands( - &self, - pkg_nv: &NpmPackageNv, - ) -> Result, AnyError> { - let package_folder = self - .npm_resolver - .resolve_package_folder_from_deno_module(pkg_nv)?; - let package_json_path = package_folder.join("package.json"); - let package_json = PackageJson::load::( - &self.npm_resolver, - &mut AllowAllNodePermissions, - package_json_path, - )?; - - Ok(match package_json.bin { - Some(Value::String(_)) => vec![pkg_nv.name.to_string()], - Some(Value::Object(o)) => { - o.into_iter().map(|(key, _)| key).collect::>() - } - _ => Vec::new(), - }) - } - - pub fn resolve_binary_export( - &self, - pkg_ref: &NpmPackageReqReference, - ) -> Result { - let pkg_nv = self - .npm_resolver - .resolve_pkg_id_from_pkg_req(&pkg_ref.req)? - .nv; - let bin_name = pkg_ref.sub_path.as_deref(); - let package_folder = self - .npm_resolver - .resolve_package_folder_from_deno_module(&pkg_nv)?; - let package_json_path = package_folder.join("package.json"); - let package_json = PackageJson::load::( - &self.npm_resolver, - &mut AllowAllNodePermissions, - package_json_path, - )?; - let bin = match &package_json.bin { - Some(bin) => bin, - None => bail!( - "package '{}' did not have a bin property in its package.json", - &pkg_nv.name, - ), - }; - let bin_entry = resolve_bin_entry_value(&pkg_nv, bin_name, bin)?; - let url = - ModuleSpecifier::from_file_path(package_folder.join(bin_entry)).unwrap(); - - let resolve_response = self.url_to_node_resolution::(url)?; - // TODO(bartlomieju): skipped checking errors for commonJS resolution and - // "preserveSymlinksMain"/"preserveSymlinks" options. - Ok(resolve_response) - } - - pub fn url_to_node_resolution( - &self, - url: ModuleSpecifier, - ) -> Result { - let url_str = url.as_str().to_lowercase(); - if url_str.starts_with("http") { - Ok(NodeResolution::Esm(url)) - } else if url_str.ends_with(".js") || url_str.ends_with(".d.ts") { - let package_config = get_closest_package_json::( - &url, - &self.npm_resolver, - &mut AllowAllNodePermissions, - )?; - if package_config.typ == "module" { - Ok(NodeResolution::Esm(url)) - } else { - Ok(NodeResolution::CommonJs(url)) - } - } else if url_str.ends_with(".mjs") || url_str.ends_with(".d.mts") { - Ok(NodeResolution::Esm(url)) - } else if url_str.ends_with(".ts") { - Err(generic_error(format!( - "TypeScript files are not supported in npm packages: {url}" - ))) - } else { - Ok(NodeResolution::CommonJs(url)) - } - } -} - -fn resolve_bin_entry_value<'a>( - pkg_nv: &NpmPackageNv, - bin_name: Option<&str>, - bin: &'a Value, -) -> Result<&'a str, AnyError> { - let bin_entry = match bin { - Value::String(_) => { - if bin_name.is_some() && bin_name.unwrap() != pkg_nv.name { - None - } else { - Some(bin) - } - } - Value::Object(o) => { - if let Some(bin_name) = bin_name { - o.get(bin_name) - } else if o.len() == 1 || o.len() > 1 && o.values().all(|v| v == o.values().next().unwrap()) { - o.values().next() - } else { - o.get(&pkg_nv.name) - } - }, - _ => bail!("package '{}' did not have a bin property with a string or object value in its package.json", pkg_nv), - }; - let bin_entry = match bin_entry { - Some(e) => e, - None => { - let keys = bin - .as_object() - .map(|o| { - o.keys() - .map(|k| format!(" * npm:{pkg_nv}/{k}")) - .collect::>() - }) - .unwrap_or_default(); - bail!( - "package '{}' did not have a bin entry for '{}' in its package.json{}", - pkg_nv, - bin_name.unwrap_or(&pkg_nv.name), - if keys.is_empty() { - "".to_string() - } else { - format!("\n\nPossibilities:\n{}", keys.join("\n")) - } - ) - } - }; - match bin_entry { - Value::String(s) => Ok(s), - _ => bail!( - "package '{}' had a non-string sub property of bin in its package.json", - pkg_nv, - ), - } -} - -fn package_config_resolve( - package_subpath: &str, - package_dir: &Path, - referrer_kind: NodeModuleKind, - conditions: &[&str], - mode: NodeResolutionMode, - npm_resolver: &dyn NpmResolver, - permissions: &mut dyn NodePermissions, -) -> Result, AnyError> { - let package_json_path = package_dir.join("package.json"); - let referrer = ModuleSpecifier::from_directory_path(package_dir).unwrap(); - let package_config = PackageJson::load::( - npm_resolver, - permissions, - package_json_path.clone(), - )?; - if let Some(exports) = &package_config.exports { - let result = package_exports_resolve::( - &package_json_path, - package_subpath.to_string(), - exports, - &referrer, - referrer_kind, - conditions, - mode, - npm_resolver, - permissions, - ); - match result { - Ok(found) => return Ok(Some(found)), - Err(exports_err) => { - if mode.is_types() && package_subpath == "." { - if let Ok(Some(path)) = - legacy_main_resolve::(&package_config, referrer_kind, mode) - { - return Ok(Some(path)); - } else { - return Ok(None); - } - } - return Err(exports_err); - } - } - } - if package_subpath == "." { - return legacy_main_resolve::(&package_config, referrer_kind, mode); - } - - Ok(Some(package_dir.join(package_subpath))) -} - -fn finalize_resolution( - resolved: ModuleSpecifier, - base: &ModuleSpecifier, -) -> Result { - let encoded_sep_re = lazy_regex::regex!(r"%2F|%2C"); - - if encoded_sep_re.is_match(resolved.path()) { - return Err(errors::err_invalid_module_specifier( - resolved.path(), - "must not include encoded \"/\" or \"\\\\\" characters", - Some(to_file_path_string(base)), - )); - } - - let path = to_file_path(&resolved); - - // TODO(bartlomieju): currently not supported - // if (getOptionValue('--experimental-specifier-resolution') === 'node') { - // ... - // } - - let p_str = path.to_str().unwrap(); - let p = if p_str.ends_with('/') { - p_str[p_str.len() - 1..].to_string() - } else { - p_str.to_string() - }; - - let (is_dir, is_file) = if let Ok(stats) = Fs::metadata(p) { - (stats.is_dir, stats.is_file) - } else { - (false, false) - }; - if is_dir { - return Err(errors::err_unsupported_dir_import( - resolved.as_str(), - base.as_str(), - )); - } else if !is_file { - return Err(errors::err_module_not_found( - resolved.as_str(), - base.as_str(), - "module", - )); - } - - Ok(resolved) -} - -fn to_file_path(url: &ModuleSpecifier) -> PathBuf { - url - .to_file_path() - .unwrap_or_else(|_| panic!("Provided URL was not file:// URL: {url}")) -} - -fn to_file_path_string(url: &ModuleSpecifier) -> String { - to_file_path(url).display().to_string() -} - -fn should_be_treated_as_relative_or_absolute_path(specifier: &str) -> bool { - if specifier.is_empty() { - return false; - } - - if specifier.starts_with('/') { - return true; - } - - is_relative_specifier(specifier) -} - -// TODO(ry) We very likely have this utility function elsewhere in Deno. -fn is_relative_specifier(specifier: &str) -> bool { - let specifier_len = specifier.len(); - let specifier_chars: Vec<_> = specifier.chars().collect(); - - if !specifier_chars.is_empty() && specifier_chars[0] == '.' { - if specifier_len == 1 || specifier_chars[1] == '/' { - return true; - } - if specifier_chars[1] == '.' - && (specifier_len == 2 || specifier_chars[2] == '/') - { - return true; - } - } - false -} - -#[cfg(test)] -mod tests { - use deno_core::serde_json::json; - - use super::*; - - #[test] - fn test_resolve_bin_entry_value() { - // should resolve the specified value - let value = json!({ - "bin1": "./value1", - "bin2": "./value2", - "test": "./value3", - }); - assert_eq!( - resolve_bin_entry_value( - &NpmPackageNv::from_str("test@1.1.1").unwrap(), - Some("bin1"), - &value - ) - .unwrap(), - "./value1" - ); - - // should resolve the value with the same name when not specified - assert_eq!( - resolve_bin_entry_value( - &NpmPackageNv::from_str("test@1.1.1").unwrap(), - None, - &value - ) - .unwrap(), - "./value3" - ); - - // should not resolve when specified value does not exist - assert_eq!( - resolve_bin_entry_value( - &NpmPackageNv::from_str("test@1.1.1").unwrap(), - Some("other"), - &value - ) - .err() - .unwrap() - .to_string(), - concat!( - "package 'test@1.1.1' did not have a bin entry for 'other' in its package.json\n", - "\n", - "Possibilities:\n", - " * npm:test@1.1.1/bin1\n", - " * npm:test@1.1.1/bin2\n", - " * npm:test@1.1.1/test" - ) - ); - - // should not resolve when default value can't be determined - assert_eq!( - resolve_bin_entry_value( - &NpmPackageNv::from_str("asdf@1.2.3").unwrap(), - None, - &value - ) - .err() - .unwrap() - .to_string(), - concat!( - "package 'asdf@1.2.3' did not have a bin entry for 'asdf' in its package.json\n", - "\n", - "Possibilities:\n", - " * npm:asdf@1.2.3/bin1\n", - " * npm:asdf@1.2.3/bin2\n", - " * npm:asdf@1.2.3/test" - ) - ); - - // should resolve since all the values are the same - let value = json!({ - "bin1": "./value", - "bin2": "./value", - }); - assert_eq!( - resolve_bin_entry_value( - &NpmPackageNv::from_str("test@1.2.3").unwrap(), - None, - &value - ) - .unwrap(), - "./value" - ); - - // should not resolve when specified and is a string - let value = json!("./value"); - assert_eq!( - resolve_bin_entry_value( - &NpmPackageNv::from_str("test@1.2.3").unwrap(), - Some("path"), - &value - ) - .err() - .unwrap() - .to_string(), - "package 'test@1.2.3' did not have a bin entry for 'path' in its package.json" - ); - } -} diff --git a/runtime/build.rs b/runtime/build.rs index d47bee9419..358a30dc59 100644 --- a/runtime/build.rs +++ b/runtime/build.rs @@ -222,7 +222,6 @@ mod startup_snapshot { impl deno_node::NodeEnv for SnapshotNodeEnv { type P = Permissions; - type Fs = deno_node::RealFs; } deno_core::extension!(runtime, @@ -324,7 +323,7 @@ mod startup_snapshot { runtime::init_ops_and_esm(), // FIXME(bartlomieju): these extensions are specified last, because they // depend on `runtime`, even though it should be other way around - deno_node::deno_node::init_ops_and_esm::(None), + deno_node::deno_node::init_ops_and_esm::(None, None), #[cfg(not(feature = "snapshot_from_snapshot"))] runtime_main::init_ops_and_esm(), ]; diff --git a/runtime/examples/hello_runtime.rs b/runtime/examples/hello_runtime.rs index f44c774fbb..f97c045b26 100644 --- a/runtime/examples/hello_runtime.rs +++ b/runtime/examples/hello_runtime.rs @@ -43,6 +43,7 @@ async fn main() -> Result<(), AnyError> { should_break_on_first_statement: false, should_wait_for_inspector_session: false, module_loader, + node_fs: None, npm_resolver: None, get_error_class_fn: Some(&get_error_class_name), cache_storage_dir: None, diff --git a/runtime/lib.rs b/runtime/lib.rs index 6745c4a565..878171913f 100644 --- a/runtime/lib.rs +++ b/runtime/lib.rs @@ -39,5 +39,4 @@ pub use worker_bootstrap::BootstrapOptions; pub struct RuntimeNodeEnv; impl deno_node::NodeEnv for RuntimeNodeEnv { type P = permissions::PermissionsContainer; - type Fs = deno_node::RealFs; } diff --git a/runtime/web_worker.rs b/runtime/web_worker.rs index 06540a9bbf..9bc5ba011f 100644 --- a/runtime/web_worker.rs +++ b/runtime/web_worker.rs @@ -37,7 +37,6 @@ use deno_core::SourceMapGetter; use deno_fs::StdFs; use deno_io::Stdio; use deno_kv::sqlite::SqliteDbHandler; -use deno_node::NpmResolver; use deno_tls::rustls::RootCertStore; use deno_web::create_entangled_message_port; use deno_web::BlobStore; @@ -333,7 +332,8 @@ pub struct WebWorkerOptions { pub root_cert_store: Option, pub seed: Option, pub module_loader: Rc, - pub npm_resolver: Option>, + pub node_fs: Option>, + pub npm_resolver: Option>, pub create_web_worker_cb: Arc, pub preload_module_cb: Arc, pub pre_execute_module_cb: Arc, @@ -444,6 +444,7 @@ impl WebWorker { deno_fs::deno_fs::init_ops::<_, PermissionsContainer>(unstable, StdFs), deno_node::deno_node::init_ops::( options.npm_resolver, + options.node_fs, ), // Runtime ops that are always initialized for WebWorkers ops::web_worker::deno_web_worker::init_ops(), diff --git a/runtime/worker.rs b/runtime/worker.rs index 5cd60604d2..56684e9925 100644 --- a/runtime/worker.rs +++ b/runtime/worker.rs @@ -33,7 +33,6 @@ use deno_core::SourceMapGetter; use deno_fs::StdFs; use deno_io::Stdio; use deno_kv::sqlite::SqliteDbHandler; -use deno_node::NpmResolver; use deno_tls::rustls::RootCertStore; use deno_web::BlobStore; use log::debug; @@ -94,7 +93,8 @@ pub struct WorkerOptions { /// If not provided runtime will error if code being /// executed tries to load modules. pub module_loader: Rc, - pub npm_resolver: Option>, + pub node_fs: Option>, + pub npm_resolver: Option>, // Callbacks invoked when creating new instance of WebWorker pub create_web_worker_cb: Arc, pub web_worker_preload_module_cb: Arc, @@ -164,6 +164,7 @@ impl Default for WorkerOptions { broadcast_channel: Default::default(), source_map_getter: Default::default(), root_cert_store: Default::default(), + node_fs: Default::default(), npm_resolver: Default::default(), blob_store: Default::default(), extensions: Default::default(), @@ -268,6 +269,7 @@ impl MainWorker { deno_fs::deno_fs::init_ops::<_, PermissionsContainer>(unstable, StdFs), deno_node::deno_node::init_ops::( options.npm_resolver, + options.node_fs, ), // Ops from this crate ops::runtime::deno_runtime::init_ops(main_module.clone()),