diff --git a/Cargo.lock b/Cargo.lock index 1f13898fe0..b2a78300c0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2074,9 +2074,9 @@ dependencies = [ [[package]] name = "deno_npm" -version = "0.27.0" +version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f818ad5dc4c206b50b5cfa6f10b4b94b127e15c8342c152768eba40c225ca23" +checksum = "4adceb4c34f10e837d0e3ae76e88dddefb13e83c05c1ef1699fa5519241c9d27" dependencies = [ "async-trait", "capacity_builder 0.5.0", diff --git a/Cargo.toml b/Cargo.toml index 48abe48305..2b52664a7f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -54,7 +54,7 @@ deno_bench_util = { version = "0.179.0", path = "./bench_util" } deno_config = { version = "=0.43.0", features = ["workspace", "sync"] } deno_lockfile = "=0.24.0" deno_media_type = { version = "0.2.3", features = ["module_specifier"] } -deno_npm = "=0.27.0" +deno_npm = "=0.27.2" deno_path_util = "=0.3.0" deno_permissions = { version = "0.44.0", path = "./runtime/permissions" } deno_runtime = { version = "0.193.0", path = "./runtime" } diff --git a/cli/factory.rs b/cli/factory.rs index 25a39cae8b..e3873b5164 100644 --- a/cli/factory.rs +++ b/cli/factory.rs @@ -11,8 +11,10 @@ use deno_core::error::AnyError; use deno_core::futures::FutureExt; use deno_core::FeatureChecker; use deno_error::JsErrorBox; +use deno_npm_cache::NpmCacheSetting; use deno_resolver::cjs::IsCjsResolutionMode; use deno_resolver::npm::managed::ManagedInNpmPkgCheckerCreateOptions; +use deno_resolver::npm::managed::NpmResolutionCell; use deno_resolver::npm::CreateInNpmPkgCheckerOptions; use deno_resolver::npm::NpmReqResolverOptions; use deno_resolver::sloppy_imports::SloppyImportsCachedFs; @@ -67,19 +69,27 @@ use crate::node::CliNodeCodeTranslator; use crate::node::CliNodeResolver; use crate::node::CliPackageJsonResolver; use crate::npm::create_cli_npm_resolver; +use crate::npm::installer::NpmInstaller; +use crate::npm::installer::NpmResolutionInstaller; use crate::npm::CliByonmNpmResolverCreateOptions; use crate::npm::CliManagedNpmResolverCreateOptions; +use crate::npm::CliNpmCache; +use crate::npm::CliNpmCacheHttpClient; +use crate::npm::CliNpmRegistryInfoProvider; use crate::npm::CliNpmResolver; use crate::npm::CliNpmResolverCreateOptions; use crate::npm::CliNpmResolverManagedSnapshotOption; +use crate::npm::CliNpmTarballCache; use crate::npm::NpmRegistryReadPermissionChecker; use crate::npm::NpmRegistryReadPermissionCheckerMode; +use crate::npm::NpmResolutionInitializer; use crate::resolver::CjsTracker; use crate::resolver::CliDenoResolver; +use crate::resolver::CliNpmGraphResolver; use crate::resolver::CliNpmReqResolver; use crate::resolver::CliResolver; -use crate::resolver::CliResolverOptions; use crate::resolver::CliSloppyImportsResolver; +use crate::resolver::FoundPackageJsonDepFlag; use crate::resolver::NpmModuleLoader; use crate::standalone::binary::DenoCompileBinaryWriter; use crate::sys::CliSys; @@ -188,6 +198,7 @@ struct CliFactoryServices { emitter: Deferred>, feature_checker: Deferred>, file_fetcher: Deferred>, + found_pkg_json_dep_flag: Arc, fs: Deferred>, global_http_cache: Deferred>, http_cache: Deferred>, @@ -202,9 +213,18 @@ struct CliFactoryServices { module_load_preparer: Deferred>, node_code_translator: Deferred>, node_resolver: Deferred>, + npm_cache: Deferred>, npm_cache_dir: Deferred>, + npm_cache_http_client: Deferred>, + npm_graph_resolver: Deferred>, + npm_installer: Deferred>, + npm_registry_info_provider: Deferred>, npm_req_resolver: Deferred>, + npm_resolution: Arc, + npm_resolution_initializer: Deferred>, + npm_resolution_installer: Deferred>, npm_resolver: Deferred>, + npm_tarball_cache: Deferred>, parsed_source_cache: Deferred>, permission_desc_parser: Deferred>>, @@ -398,6 +418,18 @@ impl CliFactory { }) } + pub fn npm_cache(&self) -> Result<&Arc, AnyError> { + self.services.npm_cache.get_or_try_init(|| { + let cli_options = self.cli_options()?; + Ok(Arc::new(CliNpmCache::new( + self.npm_cache_dir()?.clone(), + self.sys(), + NpmCacheSetting::from_cache_setting(&cli_options.cache_setting()), + cli_options.npmrc().clone(), + ))) + }) + } + pub fn npm_cache_dir(&self) -> Result<&Arc, AnyError> { self.services.npm_cache_dir.get_or_try_init(|| { let global_path = self.deno_dir()?.npm_folder_path(); @@ -410,6 +442,123 @@ impl CliFactory { }) } + pub fn npm_cache_http_client(&self) -> &Arc { + self.services.npm_cache_http_client.get_or_init(|| { + Arc::new(CliNpmCacheHttpClient::new( + self.http_client_provider().clone(), + self.text_only_progress_bar().clone(), + )) + }) + } + + pub fn npm_graph_resolver( + &self, + ) -> Result<&Arc, AnyError> { + self.services.npm_graph_resolver.get_or_try_init(|| { + let cli_options = self.cli_options()?; + Ok(Arc::new(CliNpmGraphResolver::new( + self.npm_installer_if_managed()?.cloned(), + self.services.found_pkg_json_dep_flag.clone(), + cli_options.unstable_bare_node_builtins(), + cli_options.default_npm_caching_strategy(), + ))) + }) + } + + pub fn npm_installer_if_managed( + &self, + ) -> Result>, AnyError> { + let options = self.cli_options()?; + if options.use_byonm() || options.no_npm() { + Ok(None) + } else { + Ok(Some(self.npm_installer()?)) + } + } + + pub fn npm_installer(&self) -> Result<&Arc, AnyError> { + self.services.npm_installer.get_or_try_init(|| { + let cli_options = self.cli_options()?; + Ok(Arc::new(NpmInstaller::new( + self.npm_cache()?.clone(), + Arc::new(NpmInstallDepsProvider::from_workspace( + cli_options.workspace(), + )), + self.npm_resolution().clone(), + self.npm_resolution_initializer()?.clone(), + self.npm_resolution_installer()?.clone(), + self.text_only_progress_bar(), + self.sys(), + self.npm_tarball_cache()?.clone(), + cli_options.maybe_lockfile().cloned(), + cli_options.node_modules_dir_path().cloned(), + cli_options.lifecycle_scripts_config(), + cli_options.npm_system_info(), + ))) + }) + } + + pub fn npm_registry_info_provider( + &self, + ) -> Result<&Arc, AnyError> { + self + .services + .npm_registry_info_provider + .get_or_try_init(|| { + let cli_options = self.cli_options()?; + Ok(Arc::new(CliNpmRegistryInfoProvider::new( + self.npm_cache()?.clone(), + self.npm_cache_http_client().clone(), + cli_options.npmrc().clone(), + ))) + }) + } + + pub fn npm_resolution(&self) -> &Arc { + &self.services.npm_resolution + } + + pub fn npm_resolution_initializer( + &self, + ) -> Result<&Arc, AnyError> { + self + .services + .npm_resolution_initializer + .get_or_try_init(|| { + let cli_options = self.cli_options()?; + Ok(Arc::new(NpmResolutionInitializer::new( + self.npm_registry_info_provider()?.clone(), + self.npm_resolution().clone(), + match cli_options.resolve_npm_resolution_snapshot()? { + Some(snapshot) => { + CliNpmResolverManagedSnapshotOption::Specified(Some(snapshot)) + } + None => match cli_options.maybe_lockfile() { + Some(lockfile) => { + CliNpmResolverManagedSnapshotOption::ResolveFromLockfile( + lockfile.clone(), + ) + } + None => CliNpmResolverManagedSnapshotOption::Specified(None), + }, + }, + ))) + }) + } + + pub fn npm_resolution_installer( + &self, + ) -> Result<&Arc, AnyError> { + self.services.npm_resolution_installer.get_or_try_init(|| { + let cli_options = self.cli_options()?; + Ok(Arc::new(NpmResolutionInstaller::new( + self.npm_registry_info_provider()?.clone(), + self.npm_resolution().clone(), + cli_options.maybe_lockfile().cloned(), + ))) + }) + } + pub async fn npm_resolver( &self, ) -> Result<&Arc, AnyError> { @@ -419,7 +568,7 @@ impl CliFactory { .get_or_try_init_async( async { let cli_options = self.cli_options()?; - create_cli_npm_resolver(if cli_options.use_byonm() { + Ok(create_cli_npm_resolver(if cli_options.use_byonm() { CliNpmResolverCreateOptions::Byonm( CliByonmNpmResolverCreateOptions { sys: self.sys(), @@ -438,52 +587,43 @@ impl CliFactory { }, ) } else { + self + .npm_resolution_initializer()? + .ensure_initialized() + .await?; CliNpmResolverCreateOptions::Managed( CliManagedNpmResolverCreateOptions { - http_client_provider: self.http_client_provider().clone(), - npm_install_deps_provider: Arc::new( - NpmInstallDepsProvider::from_workspace( - cli_options.workspace(), - ), - ), sys: self.sys(), - snapshot: match cli_options.resolve_npm_resolution_snapshot()? { - Some(snapshot) => { - CliNpmResolverManagedSnapshotOption::Specified(Some( - snapshot, - )) - } - None => match cli_options.maybe_lockfile() { - Some(lockfile) => { - CliNpmResolverManagedSnapshotOption::ResolveFromLockfile( - lockfile.clone(), - ) - } - None => { - CliNpmResolverManagedSnapshotOption::Specified(None) - } - }, - }, - maybe_lockfile: cli_options.maybe_lockfile().cloned(), + npm_resolution: self.npm_resolution().clone(), npm_cache_dir: self.npm_cache_dir()?.clone(), - cache_setting: cli_options.cache_setting(), - text_only_progress_bar: self.text_only_progress_bar().clone(), maybe_node_modules_path: cli_options .node_modules_dir_path() .cloned(), npm_system_info: cli_options.npm_system_info(), npmrc: cli_options.npmrc().clone(), - lifecycle_scripts: cli_options.lifecycle_scripts_config(), }, ) - }) - .await + })) } .boxed_local(), ) .await } + pub fn npm_tarball_cache( + &self, + ) -> Result<&Arc, AnyError> { + self.services.npm_tarball_cache.get_or_try_init(|| { + let cli_options = self.cli_options()?; + Ok(Arc::new(CliNpmTarballCache::new( + self.npm_cache()?.clone(), + self.npm_cache_http_client().clone(), + self.sys(), + cli_options.npmrc().clone(), + ))) + }) + } + pub fn sloppy_imports_resolver( &self, ) -> Result>, AnyError> { @@ -571,17 +711,10 @@ impl CliFactory { .resolver .get_or_try_init_async( async { - let cli_options = self.cli_options()?; - Ok(Arc::new(CliResolver::new(CliResolverOptions { - npm_resolver: if cli_options.no_npm() { - None - } else { - Some(self.npm_resolver().await?.clone()) - }, - bare_node_builtins_enabled: cli_options - .unstable_bare_node_builtins(), - deno_resolver: self.deno_resolver().await?.clone(), - }))) + Ok(Arc::new(CliResolver::new( + self.deno_resolver().await?.clone(), + self.services.found_pkg_json_dep_flag.clone(), + ))) } .boxed_local(), ) @@ -752,6 +885,7 @@ impl CliFactory { cli_options.clone(), self.module_graph_builder().await?.clone(), self.node_resolver().await?.clone(), + self.npm_installer_if_managed()?.cloned(), self.npm_resolver().await?.clone(), self.sys(), ))) @@ -777,6 +911,8 @@ impl CliFactory { cli_options.maybe_lockfile().cloned(), self.maybe_file_watcher_reporter().clone(), self.module_info_cache()?.clone(), + self.npm_graph_resolver()?.clone(), + self.npm_installer_if_managed()?.cloned(), self.npm_resolver().await?.clone(), self.parsed_source_cache().clone(), self.resolver().await?.clone(), @@ -797,7 +933,7 @@ impl CliFactory { let cli_options = self.cli_options()?; Ok(Arc::new(ModuleGraphCreator::new( cli_options.clone(), - self.npm_resolver().await?.clone(), + self.npm_installer_if_managed()?.cloned(), self.module_graph_builder().await?.clone(), self.type_checker().await?.clone(), ))) @@ -997,6 +1133,7 @@ impl CliFactory { self.sys(), )), node_resolver.clone(), + self.npm_installer_if_managed()?.cloned(), npm_resolver.clone(), pkg_json_resolver, self.root_cert_store_provider().clone(), diff --git a/cli/graph_util.rs b/cli/graph_util.rs index 8da44c76ab..17d4fef553 100644 --- a/cli/graph_util.rs +++ b/cli/graph_util.rs @@ -7,6 +7,7 @@ use std::sync::Arc; use deno_config::deno_json; use deno_config::deno_json::JsxImportSourceConfig; +use deno_config::deno_json::NodeModulesDirMode; use deno_config::workspace::JsrPackageConfig; use deno_core::error::AnyError; use deno_core::parking_lot::Mutex; @@ -51,8 +52,11 @@ use crate::cache::ModuleInfoCache; use crate::cache::ParsedSourceCache; use crate::colors; use crate::file_fetcher::CliFileFetcher; +use crate::npm::installer::NpmInstaller; +use crate::npm::installer::PackageCaching; use crate::npm::CliNpmResolver; use crate::resolver::CjsTracker; +use crate::resolver::CliNpmGraphResolver; use crate::resolver::CliResolver; use crate::resolver::CliSloppyImportsResolver; use crate::sys::CliSys; @@ -263,7 +267,7 @@ pub struct CreateGraphOptions<'a> { pub struct ModuleGraphCreator { options: Arc, - npm_resolver: Arc, + npm_installer: Option>, module_graph_builder: Arc, type_checker: Arc, } @@ -271,13 +275,13 @@ pub struct ModuleGraphCreator { impl ModuleGraphCreator { pub fn new( options: Arc, - npm_resolver: Arc, + npm_installer: Option>, module_graph_builder: Arc, type_checker: Arc, ) -> Self { Self { options, - npm_resolver, + npm_installer, module_graph_builder, type_checker, } @@ -400,9 +404,9 @@ impl ModuleGraphCreator { .build_graph_with_npm_resolution(&mut graph, options) .await?; - if let Some(npm_resolver) = self.npm_resolver.as_managed() { + if let Some(npm_installer) = &self.npm_installer { if graph.has_node_specifier && self.options.type_check_mode().is_true() { - npm_resolver.inject_synthetic_types_node_package().await?; + npm_installer.inject_synthetic_types_node_package().await?; } } @@ -497,6 +501,8 @@ pub struct ModuleGraphBuilder { lockfile: Option>, maybe_file_watcher_reporter: Option, module_info_cache: Arc, + npm_graph_resolver: Arc, + npm_installer: Option>, npm_resolver: Arc, parsed_source_cache: Arc, resolver: Arc, @@ -516,6 +522,8 @@ impl ModuleGraphBuilder { lockfile: Option>, maybe_file_watcher_reporter: Option, module_info_cache: Arc, + npm_graph_resolver: Arc, + npm_installer: Option>, npm_resolver: Arc, parsed_source_cache: Arc, resolver: Arc, @@ -532,6 +540,8 @@ impl ModuleGraphBuilder { lockfile, maybe_file_watcher_reporter, module_info_cache, + npm_graph_resolver, + npm_installer, npm_resolver, parsed_source_cache, resolver, @@ -630,10 +640,7 @@ impl ModuleGraphBuilder { Some(loader) => MutLoaderRef::Borrowed(loader), None => MutLoaderRef::Owned(self.create_graph_loader()), }; - let cli_resolver = &self.resolver; let graph_resolver = self.create_graph_resolver()?; - let graph_npm_resolver = - cli_resolver.create_graph_npm_resolver(options.npm_caching); let maybe_file_watcher_reporter = self .maybe_file_watcher_reporter .as_ref() @@ -654,7 +661,7 @@ impl ModuleGraphBuilder { executor: Default::default(), file_system: &self.sys, jsr_url_provider: &CliJsrUrlProvider, - npm_resolver: Some(&graph_npm_resolver), + npm_resolver: Some(self.npm_graph_resolver.as_ref()), module_analyzer: &analyzer, reporter: maybe_file_watcher_reporter, resolver: Some(&graph_resolver), @@ -678,16 +685,15 @@ impl ModuleGraphBuilder { if self .cli_options .node_modules_dir()? - .map(|m| m.uses_node_modules_dir()) + .map(|m| m == NodeModulesDirMode::Auto) .unwrap_or(false) { - if let Some(npm_resolver) = self.npm_resolver.as_managed() { - let already_done = - npm_resolver.ensure_top_level_package_json_install().await?; + if let Some(npm_installer) = &self.npm_installer { + let already_done = npm_installer + .ensure_top_level_package_json_install() + .await?; if !already_done && matches!(npm_caching, NpmCachingStrategy::Eager) { - npm_resolver - .cache_packages(crate::npm::PackageCaching::All) - .await?; + npm_installer.cache_packages(PackageCaching::All).await?; } } } @@ -775,11 +781,7 @@ impl ModuleGraphBuilder { None }; let parser = self.parsed_source_cache.as_capturing_parser(); - let cli_resolver = &self.resolver; let graph_resolver = self.create_graph_resolver()?; - let graph_npm_resolver = cli_resolver.create_graph_npm_resolver( - self.cli_options.default_npm_caching_strategy(), - ); graph.build_fast_check_type_graph( deno_graph::BuildFastCheckTypeGraphOptions { @@ -788,7 +790,7 @@ impl ModuleGraphBuilder { fast_check_dts: false, jsr_url_provider: &CliJsrUrlProvider, resolver: Some(&graph_resolver), - npm_resolver: Some(&graph_npm_resolver), + npm_resolver: Some(self.npm_graph_resolver.as_ref()), workspace_fast_check: options.workspace_fast_check, }, ); diff --git a/cli/lsp/documents.rs b/cli/lsp/documents.rs index f31353d436..70f55ffd62 100644 --- a/cli/lsp/documents.rs +++ b/cli/lsp/documents.rs @@ -480,7 +480,7 @@ impl Document { let is_cjs_resolver = resolver.as_is_cjs_resolver(self.file_referrer.as_ref()); let npm_resolver = - resolver.create_graph_npm_resolver(self.file_referrer.as_ref()); + resolver.as_graph_npm_resolver(self.file_referrer.as_ref()); let config_data = resolver.as_config_data(self.file_referrer.as_ref()); let jsx_import_source_config = config_data.and_then(|d| d.maybe_jsx_import_source_config()); @@ -503,7 +503,7 @@ impl Document { s, &CliJsrUrlProvider, Some(&resolver), - Some(&npm_resolver), + Some(npm_resolver.as_ref()), ), ) }) @@ -513,7 +513,7 @@ impl Document { Arc::new(d.with_new_resolver( &CliJsrUrlProvider, Some(&resolver), - Some(&npm_resolver), + Some(npm_resolver.as_ref()), )) }); is_script = self.is_script; @@ -1702,7 +1702,7 @@ fn analyze_module( ) -> (ModuleResult, ResolutionMode) { match parsed_source_result { Ok(parsed_source) => { - let npm_resolver = resolver.create_graph_npm_resolver(file_referrer); + let npm_resolver = resolver.as_graph_npm_resolver(file_referrer); let cli_resolver = resolver.as_cli_resolver(file_referrer); let is_cjs_resolver = resolver.as_is_cjs_resolver(file_referrer); let config_data = resolver.as_config_data(file_referrer); @@ -1731,7 +1731,7 @@ fn analyze_module( file_system: &deno_graph::source::NullFileSystem, jsr_url_provider: &CliJsrUrlProvider, maybe_resolver: Some(&resolver), - maybe_npm_resolver: Some(&npm_resolver), + maybe_npm_resolver: Some(npm_resolver.as_ref()), }, )), module_resolution_mode, diff --git a/cli/lsp/resolver.rs b/cli/lsp/resolver.rs index d7f7e684a9..af4ab6571f 100644 --- a/cli/lsp/resolver.rs +++ b/cli/lsp/resolver.rs @@ -9,7 +9,6 @@ use std::sync::Arc; use dashmap::DashMap; use deno_ast::MediaType; -use deno_cache_dir::file_fetcher::CacheSetting; use deno_cache_dir::npm::NpmCacheDir; use deno_cache_dir::HttpCache; use deno_config::deno_json::JsxImportSourceConfig; @@ -21,9 +20,11 @@ use deno_graph::GraphImport; use deno_graph::ModuleSpecifier; use deno_graph::Range; use deno_npm::NpmSystemInfo; +use deno_npm_cache::TarballCache; use deno_path_util::url_to_file_path; use deno_resolver::cjs::IsCjsResolutionMode; use deno_resolver::npm::managed::ManagedInNpmPkgCheckerCreateOptions; +use deno_resolver::npm::managed::NpmResolutionCell; use deno_resolver::npm::CreateInNpmPkgCheckerOptions; use deno_resolver::npm::NpmReqResolverOptions; use deno_resolver::DenoResolverOptions; @@ -42,6 +43,8 @@ use super::cache::LspCache; use super::jsr::JsrCacheResolver; use crate::args::create_default_npmrc; use crate::args::CliLockfile; +use crate::args::LifecycleScriptsConfig; +use crate::args::NpmCachingStrategy; use crate::args::NpmInstallDepsProvider; use crate::factory::Deferred; use crate::graph_util::to_node_resolution_kind; @@ -53,19 +56,25 @@ use crate::lsp::config::ConfigData; use crate::lsp::logging::lsp_warn; use crate::node::CliNodeResolver; use crate::node::CliPackageJsonResolver; -use crate::npm::create_cli_npm_resolver_for_lsp; +use crate::npm::create_cli_npm_resolver; +use crate::npm::installer::NpmInstaller; +use crate::npm::installer::NpmResolutionInstaller; use crate::npm::CliByonmNpmResolverCreateOptions; use crate::npm::CliManagedNpmResolverCreateOptions; +use crate::npm::CliNpmCache; +use crate::npm::CliNpmCacheHttpClient; +use crate::npm::CliNpmRegistryInfoProvider; use crate::npm::CliNpmResolver; use crate::npm::CliNpmResolverCreateOptions; use crate::npm::CliNpmResolverManagedSnapshotOption; use crate::npm::ManagedCliNpmResolver; +use crate::npm::NpmResolutionInitializer; use crate::resolver::CliDenoResolver; +use crate::resolver::CliNpmGraphResolver; use crate::resolver::CliNpmReqResolver; use crate::resolver::CliResolver; -use crate::resolver::CliResolverOptions; +use crate::resolver::FoundPackageJsonDepFlag; use crate::resolver::IsCjsResolver; -use crate::resolver::WorkerCliNpmGraphResolver; use crate::sys::CliSys; use crate::tsc::into_specifier_and_media_type; use crate::util::progress_bar::ProgressBar; @@ -77,6 +86,8 @@ struct LspScopeResolver { in_npm_pkg_checker: Arc, is_cjs_resolver: Arc, jsr_resolver: Option>, + npm_graph_resolver: Arc, + npm_installer: Option>, npm_resolver: Option>, node_resolver: Option>, npm_pkg_req_resolver: Option>, @@ -96,6 +107,8 @@ impl Default for LspScopeResolver { in_npm_pkg_checker: factory.in_npm_pkg_checker().clone(), is_cjs_resolver: factory.is_cjs_resolver().clone(), jsr_resolver: None, + npm_graph_resolver: factory.npm_graph_resolver().clone(), + npm_installer: None, npm_resolver: None, node_resolver: None, npm_pkg_req_resolver: None, @@ -121,6 +134,7 @@ impl LspScopeResolver { } let in_npm_pkg_checker = factory.in_npm_pkg_checker().clone(); let npm_resolver = factory.npm_resolver().cloned(); + let npm_installer = factory.npm_installer().cloned(); let node_resolver = factory.node_resolver().cloned(); let npm_pkg_req_resolver = factory.npm_pkg_req_resolver().cloned(); let cli_resolver = factory.cli_resolver().clone(); @@ -133,8 +147,7 @@ impl LspScopeResolver { cache.for_specifier(config_data.map(|d| d.scope.as_ref())), config_data.and_then(|d| d.lockfile.clone()), ))); - let npm_graph_resolver = cli_resolver - .create_graph_npm_resolver(crate::graph_util::NpmCachingStrategy::Eager); + let npm_graph_resolver = factory.npm_graph_resolver(); let maybe_jsx_import_source_config = config_data.and_then(|d| d.maybe_jsx_import_source_config()); let graph_imports = config_data @@ -156,7 +169,7 @@ impl LspScopeResolver { imports, &CliJsrUrlProvider, Some(&resolver), - Some(&npm_graph_resolver), + Some(npm_graph_resolver.as_ref()), ); (referrer, graph_import) }) @@ -207,8 +220,10 @@ impl LspScopeResolver { in_npm_pkg_checker, is_cjs_resolver: factory.is_cjs_resolver().clone(), jsr_resolver, + npm_graph_resolver: factory.npm_graph_resolver().clone(), npm_pkg_req_resolver, npm_resolver, + npm_installer, node_resolver, pkg_json_resolver, redirect_resolver, @@ -231,6 +246,9 @@ impl LspScopeResolver { in_npm_pkg_checker: factory.in_npm_pkg_checker().clone(), is_cjs_resolver: factory.is_cjs_resolver().clone(), jsr_resolver: self.jsr_resolver.clone(), + npm_graph_resolver: factory.npm_graph_resolver().clone(), + // npm installer isn't necessary for a snapshot + npm_installer: None, npm_pkg_req_resolver: factory.npm_pkg_req_resolver().cloned(), npm_resolver: factory.npm_resolver().cloned(), node_resolver: factory.node_resolver().cloned(), @@ -318,14 +336,12 @@ impl LspResolver { if let Some(dep_info) = dep_info { *resolver.dep_info.lock() = dep_info.clone(); } - if let Some(npm_resolver) = resolver.npm_resolver.as_ref() { - if let Some(npm_resolver) = npm_resolver.as_managed() { - let reqs = dep_info - .map(|i| i.npm_reqs.iter().cloned().collect::>()) - .unwrap_or_default(); - if let Err(err) = npm_resolver.set_package_reqs(&reqs).await { - lsp_warn!("Could not set npm package requirements: {:#}", err); - } + if let Some(npm_installer) = resolver.npm_installer.as_ref() { + let reqs = dep_info + .map(|i| i.npm_reqs.iter().cloned().collect::>()) + .unwrap_or_default(); + if let Err(err) = npm_installer.set_package_reqs(&reqs).await { + lsp_warn!("Could not set npm package requirements: {:#}", err); } } } @@ -339,14 +355,12 @@ impl LspResolver { resolver.resolver.as_ref() } - pub fn create_graph_npm_resolver( + pub fn as_graph_npm_resolver( &self, file_referrer: Option<&ModuleSpecifier>, - ) -> WorkerCliNpmGraphResolver { + ) -> &Arc { let resolver = self.get_scope_resolver(file_referrer); - resolver - .resolver - .create_graph_npm_resolver(crate::graph_util::NpmCachingStrategy::Eager) + &resolver.npm_graph_resolver } pub fn as_is_cjs_resolver( @@ -590,9 +604,12 @@ pub struct ScopeDepInfo { #[derive(Default)] struct ResolverFactoryServices { cli_resolver: Deferred>, + found_pkg_json_dep_flag: Arc, in_npm_pkg_checker: Deferred>, is_cjs_resolver: Deferred>, node_resolver: Deferred>>, + npm_graph_resolver: Deferred>, + npm_installer: Option>, npm_pkg_req_resolver: Deferred>>, npm_resolver: Option>, } @@ -616,6 +633,10 @@ impl<'a> ResolverFactory<'a> { } } + // todo(dsherret): probably this method could be removed in the future + // and instead just `npm_resolution_initializer.ensure_initialized()` could + // be called. The reason this exists is because creating the npm resolvers + // used to be async. async fn init_npm_resolver( &mut self, http_client_provider: &Arc, @@ -645,11 +666,31 @@ impl<'a> ResolverFactory<'a> { cache.deno_dir().npm_folder_path(), npmrc.get_all_known_registries_urls(), )); - CliNpmResolverCreateOptions::Managed(CliManagedNpmResolverCreateOptions { - http_client_provider: http_client_provider.clone(), - // only used for top level install, so we can ignore this - npm_install_deps_provider: Arc::new(NpmInstallDepsProvider::empty()), - snapshot: match self.config_data.and_then(|d| d.lockfile.as_ref()) { + let npm_cache = Arc::new(CliNpmCache::new( + npm_cache_dir.clone(), + sys.clone(), + // Use an "only" cache setting in order to make the + // user do an explicit "cache" command and prevent + // the cache from being filled with lots of packages while + // the user is typing. + deno_npm_cache::NpmCacheSetting::Only, + npmrc.clone(), + )); + let pb = ProgressBar::new(ProgressBarStyle::TextOnly); + let npm_client = Arc::new(CliNpmCacheHttpClient::new( + http_client_provider.clone(), + pb.clone(), + )); + let registry_info_provider = Arc::new(CliNpmRegistryInfoProvider::new( + npm_cache.clone(), + npm_client.clone(), + npmrc.clone(), + )); + let npm_resolution = Arc::new(NpmResolutionCell::default()); + let npm_resolution_initializer = Arc::new(NpmResolutionInitializer::new( + registry_info_provider.clone(), + npm_resolution.clone(), + match self.config_data.and_then(|d| d.lockfile.as_ref()) { Some(lockfile) => { CliNpmResolverManagedSnapshotOption::ResolveFromLockfile( lockfile.clone(), @@ -657,26 +698,62 @@ impl<'a> ResolverFactory<'a> { } None => CliNpmResolverManagedSnapshotOption::Specified(None), }, + )); + // Don't provide the lockfile. We don't want these resolvers + // updating it. Only the cache request should update the lockfile. + let maybe_lockfile: Option> = None; + let maybe_node_modules_path = + self.config_data.and_then(|d| d.node_modules_dir.clone()); + let tarball_cache = Arc::new(TarballCache::new( + npm_cache.clone(), + npm_client.clone(), + sys.clone(), + npmrc.clone(), + )); + let npm_resolution_installer = Arc::new(NpmResolutionInstaller::new( + registry_info_provider, + npm_resolution.clone(), + maybe_lockfile.clone(), + )); + let npm_installer = Arc::new(NpmInstaller::new( + npm_cache.clone(), + Arc::new(NpmInstallDepsProvider::empty()), + npm_resolution.clone(), + npm_resolution_initializer.clone(), + npm_resolution_installer, + &pb, + sys.clone(), + tarball_cache.clone(), + maybe_lockfile, + maybe_node_modules_path.clone(), + LifecycleScriptsConfig::default(), + NpmSystemInfo::default(), + )); + self.set_npm_installer(npm_installer); + // spawn due to the lsp's `Send` requirement + deno_core::unsync::spawn(async move { + if let Err(err) = npm_resolution_initializer.ensure_initialized().await + { + log::warn!("failed to initialize npm resolution: {}", err); + } + }) + .await + .unwrap(); + + CliNpmResolverCreateOptions::Managed(CliManagedNpmResolverCreateOptions { sys: CliSys::default(), npm_cache_dir, - // Use an "only" cache setting in order to make the - // user do an explicit "cache" command and prevent - // the cache from being filled with lots of packages while - // the user is typing. - cache_setting: CacheSetting::Only, - text_only_progress_bar: ProgressBar::new(ProgressBarStyle::TextOnly), - // Don't provide the lockfile. We don't want these resolvers - // updating it. Only the cache request should update the lockfile. - maybe_lockfile: None, - maybe_node_modules_path: self - .config_data - .and_then(|d| d.node_modules_dir.clone()), + maybe_node_modules_path, npmrc, + npm_resolution, npm_system_info: NpmSystemInfo::default(), - lifecycle_scripts: Default::default(), }) }; - self.set_npm_resolver(create_cli_npm_resolver_for_lsp(options).await); + self.set_npm_resolver(create_cli_npm_resolver(options)); + } + + pub fn set_npm_installer(&mut self, npm_installer: Arc) { + self.services.npm_installer = Some(npm_installer); } pub fn set_npm_resolver(&mut self, npm_resolver: Arc) { @@ -720,13 +797,27 @@ impl<'a> ResolverFactory<'a> { is_byonm: self.config_data.map(|d| d.byonm).unwrap_or(false), maybe_vendor_dir: self.config_data.and_then(|d| d.vendor_dir.as_ref()), })); - Arc::new(CliResolver::new(CliResolverOptions { + Arc::new(CliResolver::new( deno_resolver, - npm_resolver: self.npm_resolver().cloned(), - bare_node_builtins_enabled: self + self.services.found_pkg_json_dep_flag.clone(), + )) + }) + } + + pub fn npm_installer(&self) -> Option<&Arc> { + self.services.npm_installer.as_ref() + } + + pub fn npm_graph_resolver(&self) -> &Arc { + self.services.npm_graph_resolver.get_or_init(|| { + Arc::new(CliNpmGraphResolver::new( + None, + self.services.found_pkg_json_dep_flag.clone(), + self .config_data .is_some_and(|d| d.unstable.contains("bare-node-builtins")), - })) + NpmCachingStrategy::Eager, + )) }) } diff --git a/cli/main.rs b/cli/main.rs index 6bbefcf956..f97ea81e5d 100644 --- a/cli/main.rs +++ b/cli/main.rs @@ -40,7 +40,6 @@ use deno_core::error::AnyError; use deno_core::error::CoreError; use deno_core::futures::FutureExt; use deno_core::unsync::JoinHandle; -use deno_npm::resolution::SnapshotFromLockfileError; use deno_resolver::npm::ByonmResolvePkgFolderFromDenoReqError; use deno_resolver::npm::ResolvePkgFolderFromDenoReqError; use deno_runtime::fmt_errors::format_js_error; @@ -52,6 +51,7 @@ use factory::CliFactory; use standalone::MODULE_NOT_FOUND; use standalone::UNSUPPORTED_SCHEME; +use self::npm::ResolveSnapshotError; use crate::args::flags_from_vec; use crate::args::DenoSubcommand; use crate::args::Flags; @@ -376,13 +376,15 @@ fn exit_for_error(error: AnyError) -> ! { util::result::any_and_jserrorbox_downcast_ref::(&error) { error_string = format_js_error(e); - } else if let Some(SnapshotFromLockfileError::IntegrityCheckFailed(e)) = - util::result::any_and_jserrorbox_downcast_ref::( + } else if let Some(e @ ResolveSnapshotError { .. }) = + util::result::any_and_jserrorbox_downcast_ref::( &error, ) { - error_string = e.to_string(); - error_code = 10; + if let Some(e) = e.maybe_integrity_check_error() { + error_string = e.to_string(); + error_code = 10; + } } exit_with_message(&error_string, error_code); diff --git a/cli/npm/managed/installers/common/bin_entries.rs b/cli/npm/installer/common/bin_entries.rs similarity index 99% rename from cli/npm/managed/installers/common/bin_entries.rs rename to cli/npm/installer/common/bin_entries.rs index bc69786b6c..2f7bed285a 100644 --- a/cli/npm/managed/installers/common/bin_entries.rs +++ b/cli/npm/installer/common/bin_entries.rs @@ -8,8 +8,7 @@ use std::path::PathBuf; use deno_npm::resolution::NpmResolutionSnapshot; use deno_npm::NpmPackageId; - -use crate::npm::managed::NpmResolutionPackage; +use deno_npm::NpmResolutionPackage; #[derive(Default)] pub struct BinEntries<'a> { diff --git a/cli/npm/managed/installers/common/lifecycle_scripts.rs b/cli/npm/installer/common/lifecycle_scripts.rs similarity index 100% rename from cli/npm/managed/installers/common/lifecycle_scripts.rs rename to cli/npm/installer/common/lifecycle_scripts.rs diff --git a/cli/npm/managed/installers/common/mod.rs b/cli/npm/installer/common/mod.rs similarity index 79% rename from cli/npm/managed/installers/common/mod.rs rename to cli/npm/installer/common/mod.rs index 9659649a2e..bd22a58f03 100644 --- a/cli/npm/managed/installers/common/mod.rs +++ b/cli/npm/installer/common/mod.rs @@ -3,14 +3,14 @@ use async_trait::async_trait; use deno_error::JsErrorBox; -use crate::npm::PackageCaching; +use super::PackageCaching; pub mod bin_entries; pub mod lifecycle_scripts; /// Part of the resolution that interacts with the file system. #[async_trait(?Send)] -pub trait NpmPackageFsInstaller: Send + Sync { +pub trait NpmPackageFsInstaller: std::fmt::Debug + Send + Sync { async fn cache_packages<'a>( &self, caching: PackageCaching<'a>, diff --git a/cli/npm/managed/installers/global.rs b/cli/npm/installer/global.rs similarity index 97% rename from cli/npm/managed/installers/global.rs rename to cli/npm/installer/global.rs index 477d73dbfb..a6b296c6d8 100644 --- a/cli/npm/managed/installers/global.rs +++ b/cli/npm/installer/global.rs @@ -11,14 +11,14 @@ use deno_core::futures::StreamExt; use deno_error::JsErrorBox; use deno_npm::NpmResolutionPackage; use deno_npm::NpmSystemInfo; -use deno_resolver::npm::managed::NpmResolution; +use deno_resolver::npm::managed::NpmResolutionCell; use super::common::lifecycle_scripts::LifecycleScriptsStrategy; use super::common::NpmPackageFsInstaller; +use super::PackageCaching; use crate::args::LifecycleScriptsConfig; use crate::cache::FastInsecureHasher; use crate::colors; -use crate::npm::managed::PackageCaching; use crate::npm::CliNpmCache; use crate::npm::CliNpmTarballCache; @@ -27,25 +27,25 @@ use crate::npm::CliNpmTarballCache; pub struct GlobalNpmPackageInstaller { cache: Arc, tarball_cache: Arc, - resolution: Arc, - system_info: NpmSystemInfo, + resolution: Arc, lifecycle_scripts: LifecycleScriptsConfig, + system_info: NpmSystemInfo, } impl GlobalNpmPackageInstaller { pub fn new( cache: Arc, tarball_cache: Arc, - resolution: Arc, - system_info: NpmSystemInfo, + resolution: Arc, lifecycle_scripts: LifecycleScriptsConfig, + system_info: NpmSystemInfo, ) -> Self { Self { cache, tarball_cache, resolution, - system_info, lifecycle_scripts, + system_info, } } } diff --git a/cli/npm/managed/installers/local.rs b/cli/npm/installer/local.rs similarity index 99% rename from cli/npm/managed/installers/local.rs rename to cli/npm/installer/local.rs index a8efaaeb00..87288c6c8e 100644 --- a/cli/npm/managed/installers/local.rs +++ b/cli/npm/installer/local.rs @@ -25,7 +25,7 @@ use deno_npm::NpmResolutionPackage; use deno_npm::NpmSystemInfo; use deno_path_util::fs::atomic_write_file_with_retries; use deno_resolver::npm::get_package_folder_id_folder_name; -use deno_resolver::npm::managed::NpmResolution; +use deno_resolver::npm::managed::NpmResolutionCell; use deno_semver::package::PackageNv; use deno_semver::StackString; use serde::Deserialize; @@ -33,11 +33,11 @@ use serde::Serialize; use super::common::bin_entries; use super::common::NpmPackageFsInstaller; +use super::PackageCaching; use crate::args::LifecycleScriptsConfig; use crate::args::NpmInstallDepsProvider; use crate::cache::CACHE_PERM; use crate::colors; -use crate::npm::managed::PackageCaching; use crate::npm::CliNpmCache; use crate::npm::CliNpmTarballCache; use crate::sys::CliSys; @@ -54,12 +54,12 @@ pub struct LocalNpmPackageInstaller { cache: Arc, npm_install_deps_provider: Arc, progress_bar: ProgressBar, - resolution: Arc, + resolution: Arc, sys: CliSys, tarball_cache: Arc, + lifecycle_scripts: LifecycleScriptsConfig, root_node_modules_path: PathBuf, system_info: NpmSystemInfo, - lifecycle_scripts: LifecycleScriptsConfig, } impl LocalNpmPackageInstaller { @@ -68,12 +68,12 @@ impl LocalNpmPackageInstaller { cache: Arc, npm_install_deps_provider: Arc, progress_bar: ProgressBar, - resolution: Arc, + resolution: Arc, sys: CliSys, tarball_cache: Arc, node_modules_folder: PathBuf, - system_info: NpmSystemInfo, lifecycle_scripts: LifecycleScriptsConfig, + system_info: NpmSystemInfo, ) -> Self { Self { cache, @@ -82,9 +82,9 @@ impl LocalNpmPackageInstaller { resolution, tarball_cache, sys, + lifecycle_scripts, root_node_modules_path: node_modules_folder, system_info, - lifecycle_scripts, } } } diff --git a/cli/npm/installer/mod.rs b/cli/npm/installer/mod.rs new file mode 100644 index 0000000000..58b9cb1bc7 --- /dev/null +++ b/cli/npm/installer/mod.rs @@ -0,0 +1,283 @@ +// Copyright 2018-2025 the Deno authors. MIT license. + +use std::borrow::Cow; +use std::path::PathBuf; +use std::sync::Arc; + +use deno_core::error::AnyError; +use deno_core::unsync::sync::AtomicFlag; +use deno_error::JsErrorBox; +use deno_npm::registry::NpmPackageInfo; +use deno_npm::registry::NpmRegistryPackageInfoLoadError; +use deno_npm::NpmSystemInfo; +use deno_resolver::npm::managed::NpmResolutionCell; +use deno_runtime::colors; +use deno_semver::package::PackageReq; + +pub use self::common::NpmPackageFsInstaller; +use self::global::GlobalNpmPackageInstaller; +use self::local::LocalNpmPackageInstaller; +pub use self::resolution::AddPkgReqsResult; +pub use self::resolution::NpmResolutionInstaller; +use super::NpmResolutionInitializer; +use crate::args::CliLockfile; +use crate::args::LifecycleScriptsConfig; +use crate::args::NpmInstallDepsProvider; +use crate::args::PackageJsonDepValueParseWithLocationError; +use crate::npm::CliNpmCache; +use crate::npm::CliNpmTarballCache; +use crate::sys::CliSys; +use crate::util::progress_bar::ProgressBar; + +mod common; +mod global; +mod local; +mod resolution; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum PackageCaching<'a> { + Only(Cow<'a, [PackageReq]>), + All, +} + +#[derive(Debug)] +pub struct NpmInstaller { + fs_installer: Arc, + npm_install_deps_provider: Arc, + npm_resolution_initializer: Arc, + npm_resolution_installer: Arc, + maybe_lockfile: Option>, + npm_resolution: Arc, + top_level_install_flag: AtomicFlag, +} + +impl NpmInstaller { + #[allow(clippy::too_many_arguments)] + pub fn new( + npm_cache: Arc, + npm_install_deps_provider: Arc, + npm_resolution: Arc, + npm_resolution_initializer: Arc, + npm_resolution_installer: Arc, + progress_bar: &ProgressBar, + sys: CliSys, + tarball_cache: Arc, + maybe_lockfile: Option>, + maybe_node_modules_path: Option, + lifecycle_scripts: LifecycleScriptsConfig, + system_info: NpmSystemInfo, + ) -> Self { + let fs_installer: Arc = + match maybe_node_modules_path { + Some(node_modules_folder) => Arc::new(LocalNpmPackageInstaller::new( + npm_cache, + npm_install_deps_provider.clone(), + progress_bar.clone(), + npm_resolution.clone(), + sys, + tarball_cache, + node_modules_folder, + lifecycle_scripts, + system_info, + )), + None => Arc::new(GlobalNpmPackageInstaller::new( + npm_cache, + tarball_cache, + npm_resolution.clone(), + lifecycle_scripts, + system_info, + )), + }; + Self { + fs_installer, + npm_install_deps_provider, + npm_resolution, + npm_resolution_initializer, + npm_resolution_installer, + maybe_lockfile, + top_level_install_flag: Default::default(), + } + } + + /// Adds package requirements to the resolver and ensures everything is setup. + /// This includes setting up the `node_modules` directory, if applicable. + pub async fn add_and_cache_package_reqs( + &self, + packages: &[PackageReq], + ) -> Result<(), JsErrorBox> { + self.npm_resolution_initializer.ensure_initialized().await?; + self + .add_package_reqs_raw( + packages, + Some(PackageCaching::Only(packages.into())), + ) + .await + .dependencies_result + } + + pub async fn add_package_reqs_no_cache( + &self, + packages: &[PackageReq], + ) -> Result<(), JsErrorBox> { + self.npm_resolution_initializer.ensure_initialized().await?; + self + .add_package_reqs_raw(packages, None) + .await + .dependencies_result + } + + pub async fn add_package_reqs( + &self, + packages: &[PackageReq], + caching: PackageCaching<'_>, + ) -> Result<(), JsErrorBox> { + self + .add_package_reqs_raw(packages, Some(caching)) + .await + .dependencies_result + } + + pub async fn add_package_reqs_raw<'a>( + &self, + packages: &[PackageReq], + caching: Option>, + ) -> AddPkgReqsResult { + if packages.is_empty() { + return AddPkgReqsResult { + dependencies_result: Ok(()), + results: vec![], + }; + } + + #[cfg(debug_assertions)] + self.npm_resolution_initializer.debug_assert_initialized(); + + let mut result = self + .npm_resolution_installer + .add_package_reqs(packages) + .await; + + if result.dependencies_result.is_ok() { + if let Some(lockfile) = self.maybe_lockfile.as_ref() { + result.dependencies_result = lockfile.error_if_changed(); + } + } + if result.dependencies_result.is_ok() { + if let Some(caching) = caching { + result.dependencies_result = self.cache_packages(caching).await; + } + } + + result + } + + /// Sets package requirements to the resolver, removing old requirements and adding new ones. + /// + /// This will retrieve and resolve package information, but not cache any package files. + pub async fn set_package_reqs( + &self, + packages: &[PackageReq], + ) -> Result<(), AnyError> { + self + .npm_resolution_installer + .set_package_reqs(packages) + .await + } + + pub async fn inject_synthetic_types_node_package( + &self, + ) -> Result<(), JsErrorBox> { + self.npm_resolution_initializer.ensure_initialized().await?; + let reqs = &[PackageReq::from_str("@types/node").unwrap()]; + // add and ensure this isn't added to the lockfile + self + .add_package_reqs(reqs, PackageCaching::Only(reqs.into())) + .await?; + + Ok(()) + } + + pub async fn cache_package_info( + &self, + package_name: &str, + ) -> Result, NpmRegistryPackageInfoLoadError> { + self + .npm_resolution_installer + .cache_package_info(package_name) + .await + } + + pub async fn cache_packages( + &self, + caching: PackageCaching<'_>, + ) -> Result<(), JsErrorBox> { + self.npm_resolution_initializer.ensure_initialized().await?; + self.fs_installer.cache_packages(caching).await + } + + pub fn ensure_no_pkg_json_dep_errors( + &self, + ) -> Result<(), Box> { + for err in self.npm_install_deps_provider.pkg_json_dep_errors() { + match err.source.as_kind() { + deno_package_json::PackageJsonDepValueParseErrorKind::VersionReq(_) => { + return Err(Box::new(err.clone())); + } + deno_package_json::PackageJsonDepValueParseErrorKind::Unsupported { + .. + } => { + // only warn for this one + log::warn!( + "{} {}\n at {}", + colors::yellow("Warning"), + err.source, + err.location, + ) + } + } + } + Ok(()) + } + + /// Ensures that the top level `package.json` dependencies are installed. + /// This may set up the `node_modules` directory. + /// + /// Returns `true` if the top level packages are already installed. A + /// return value of `false` means that new packages were added to the NPM resolution. + pub async fn ensure_top_level_package_json_install( + &self, + ) -> Result { + if !self.top_level_install_flag.raise() { + return Ok(true); // already did this + } + + self.npm_resolution_initializer.ensure_initialized().await?; + + let pkg_json_remote_pkgs = self.npm_install_deps_provider.remote_pkgs(); + if pkg_json_remote_pkgs.is_empty() { + return Ok(true); + } + + // check if something needs resolving before bothering to load all + // the package information (which is slow) + if pkg_json_remote_pkgs.iter().all(|pkg| { + self + .npm_resolution + .resolve_pkg_id_from_pkg_req(&pkg.req) + .is_ok() + }) { + log::debug!( + "All package.json deps resolvable. Skipping top level install." + ); + return Ok(true); // everything is already resolvable + } + + let pkg_reqs = pkg_json_remote_pkgs + .iter() + .map(|pkg| pkg.req.clone()) + .collect::>(); + self.add_package_reqs_no_cache(&pkg_reqs).await?; + + Ok(false) + } +} diff --git a/cli/npm/managed/installer.rs b/cli/npm/installer/resolution.rs similarity index 92% rename from cli/npm/managed/installer.rs rename to cli/npm/installer/resolution.rs index 9f1cc05968..06bbcd4f76 100644 --- a/cli/npm/managed/installer.rs +++ b/cli/npm/installer/resolution.rs @@ -8,12 +8,14 @@ use deno_core::error::AnyError; use deno_error::JsErrorBox; use deno_lockfile::NpmPackageDependencyLockfileInfo; use deno_lockfile::NpmPackageLockfileInfo; +use deno_npm::registry::NpmPackageInfo; use deno_npm::registry::NpmRegistryApi; +use deno_npm::registry::NpmRegistryPackageInfoLoadError; use deno_npm::resolution::AddPkgReqsOptions; use deno_npm::resolution::NpmResolutionError; use deno_npm::resolution::NpmResolutionSnapshot; use deno_npm::NpmResolutionPackage; -use deno_resolver::npm::managed::NpmResolution; +use deno_resolver::npm::managed::NpmResolutionCell; use deno_semver::jsr::JsrDepPackageReq; use deno_semver::package::PackageNv; use deno_semver::package::PackageReq; @@ -35,9 +37,10 @@ pub struct AddPkgReqsResult { } /// Updates the npm resolution with the provided package requirements. +#[derive(Debug)] pub struct NpmResolutionInstaller { registry_info_provider: Arc, - resolution: Arc, + resolution: Arc, maybe_lockfile: Option>, update_queue: TaskQueue, } @@ -45,7 +48,7 @@ pub struct NpmResolutionInstaller { impl NpmResolutionInstaller { pub fn new( registry_info_provider: Arc, - resolution: Arc, + resolution: Arc, maybe_lockfile: Option>, ) -> Self { Self { @@ -56,6 +59,14 @@ impl NpmResolutionInstaller { } } + pub async fn cache_package_info( + &self, + package_name: &str, + ) -> Result, NpmRegistryPackageInfoLoadError> { + // this will internally cache the package information + self.registry_info_provider.package_info(package_name).await + } + pub async fn add_package_reqs( &self, package_reqs: &[PackageReq], diff --git a/cli/npm/managed.rs b/cli/npm/managed.rs new file mode 100644 index 0000000000..0d4c209acc --- /dev/null +++ b/cli/npm/managed.rs @@ -0,0 +1,496 @@ +// Copyright 2018-2025 the Deno authors. MIT license. + +use std::path::Path; +use std::path::PathBuf; +use std::sync::Arc; + +use deno_ast::ModuleSpecifier; +use deno_cache_dir::npm::NpmCacheDir; +use deno_core::parking_lot::Mutex; +use deno_core::serde_json; +use deno_core::url::Url; +use deno_error::JsError; +use deno_error::JsErrorBox; +use deno_npm::npm_rc::ResolvedNpmRc; +use deno_npm::registry::NpmRegistryApi; +use deno_npm::resolution::NpmResolutionSnapshot; +use deno_npm::resolution::PackageReqNotFoundError; +use deno_npm::resolution::ValidSerializedNpmResolutionSnapshot; +use deno_npm::NpmPackageId; +use deno_npm::NpmResolutionPackage; +use deno_npm::NpmSystemInfo; +use deno_resolver::npm::managed::NpmResolutionCell; +use deno_resolver::npm::managed::ResolvePkgFolderFromDenoModuleError; +use deno_resolver::npm::managed::ResolvePkgFolderFromPkgIdError; +use deno_resolver::npm::managed::ResolvePkgIdFromSpecifierError; +use deno_resolver::npm::ByonmOrManagedNpmResolver; +use deno_resolver::npm::ManagedNpmResolver; +use deno_resolver::npm::ResolvePkgFolderFromDenoReqError; +use deno_runtime::ops::process::NpmProcessStateProvider; +use deno_semver::package::PackageNv; +use deno_semver::package::PackageReq; +use node_resolver::NpmPackageFolderResolver; +use sys_traits::FsMetadata; +use thiserror::Error; + +use super::CliNpmRegistryInfoProvider; +use super::CliNpmResolver; +use super::InnerCliNpmResolverRef; +use crate::args::CliLockfile; +use crate::args::NpmProcessState; +use crate::args::NpmProcessStateKind; +use crate::cache::FastInsecureHasher; +use crate::sys::CliSys; + +#[derive(Debug, Clone)] +pub enum CliNpmResolverManagedSnapshotOption { + ResolveFromLockfile(Arc), + Specified(Option), +} + +#[derive(Debug)] +enum SyncState { + Pending(Option), + Err(ResolveSnapshotError), + Success, +} + +#[derive(Debug)] +pub struct NpmResolutionInitializer { + npm_registry_info_provider: Arc, + npm_resolution: Arc, + queue: tokio::sync::Mutex<()>, + sync_state: Mutex, +} + +impl NpmResolutionInitializer { + pub fn new( + npm_registry_info_provider: Arc, + npm_resolution: Arc, + snapshot_option: CliNpmResolverManagedSnapshotOption, + ) -> Self { + Self { + npm_registry_info_provider, + npm_resolution, + queue: tokio::sync::Mutex::new(()), + sync_state: Mutex::new(SyncState::Pending(Some(snapshot_option))), + } + } + + #[cfg(debug_assertions)] + pub fn debug_assert_initialized(&self) { + if !matches!(*self.sync_state.lock(), SyncState::Success) { + panic!("debug assert: npm resolution must be initialized before calling this code"); + } + } + + pub async fn ensure_initialized(&self) -> Result<(), JsErrorBox> { + // fast exit if not pending + { + match &*self.sync_state.lock() { + SyncState::Pending(_) => {} + SyncState::Err(err) => return Err(JsErrorBox::from_err(err.clone())), + SyncState::Success => return Ok(()), + } + } + + // only allow one task in here at a time + let _guard = self.queue.lock().await; + + let snapshot_option = { + let mut sync_state = self.sync_state.lock(); + match &mut *sync_state { + SyncState::Pending(snapshot_option) => { + // this should never panic, but if it does it means that a + // previous future was dropped while initialization occurred... + // that should never happen because this is initialized during + // startup + snapshot_option.take().unwrap() + } + // another thread updated the state while we were waiting + SyncState::Err(resolve_snapshot_error) => { + return Err(JsErrorBox::from_err(resolve_snapshot_error.clone())); + } + SyncState::Success => { + return Ok(()); + } + } + }; + + match resolve_snapshot(&self.npm_registry_info_provider, snapshot_option) + .await + { + Ok(maybe_snapshot) => { + if let Some(snapshot) = maybe_snapshot { + self + .npm_resolution + .set_snapshot(NpmResolutionSnapshot::new(snapshot)); + } + let mut sync_state = self.sync_state.lock(); + *sync_state = SyncState::Success; + Ok(()) + } + Err(err) => { + let mut sync_state = self.sync_state.lock(); + *sync_state = SyncState::Err(err.clone()); + Err(JsErrorBox::from_err(err)) + } + } + } +} + +pub struct CliManagedNpmResolverCreateOptions { + pub npm_cache_dir: Arc, + pub sys: CliSys, + pub maybe_node_modules_path: Option, + pub npm_system_info: NpmSystemInfo, + pub npmrc: Arc, + pub npm_resolution: Arc, +} + +pub fn create_managed_npm_resolver( + options: CliManagedNpmResolverCreateOptions, +) -> Arc { + let managed_npm_resolver = + Arc::new(ManagedNpmResolver::::new::( + &options.npm_cache_dir, + &options.npmrc, + options.npm_resolution.clone(), + options.sys.clone(), + options.maybe_node_modules_path, + )); + Arc::new(ManagedCliNpmResolver::new( + managed_npm_resolver, + options.npm_cache_dir, + options.npmrc, + options.npm_resolution, + options.sys, + options.npm_system_info, + )) +} + +#[derive(Debug, Error, Clone, JsError)] +#[error("failed reading lockfile '{}'", lockfile_path.display())] +#[class(inherit)] +pub struct ResolveSnapshotError { + lockfile_path: PathBuf, + #[inherit] + #[source] + source: SnapshotFromLockfileError, +} + +impl ResolveSnapshotError { + pub fn maybe_integrity_check_error( + &self, + ) -> Option<&deno_npm::resolution::IntegrityCheckFailedError> { + match &self.source { + SnapshotFromLockfileError::SnapshotFromLockfile( + deno_npm::resolution::SnapshotFromLockfileError::IntegrityCheckFailed( + err, + ), + ) => Some(err), + _ => None, + } + } +} + +async fn resolve_snapshot( + registry_info_provider: &Arc, + snapshot: CliNpmResolverManagedSnapshotOption, +) -> Result, ResolveSnapshotError> +{ + match snapshot { + CliNpmResolverManagedSnapshotOption::ResolveFromLockfile(lockfile) => { + if !lockfile.overwrite() { + let snapshot = snapshot_from_lockfile( + lockfile.clone(), + ®istry_info_provider.as_npm_registry_api(), + ) + .await + .map_err(|source| ResolveSnapshotError { + lockfile_path: lockfile.filename.clone(), + source, + })?; + Ok(Some(snapshot)) + } else { + Ok(None) + } + } + CliNpmResolverManagedSnapshotOption::Specified(snapshot) => Ok(snapshot), + } +} + +#[derive(Debug, Error, Clone, JsError)] +pub enum SnapshotFromLockfileError { + #[error(transparent)] + #[class(inherit)] + IncompleteError( + #[from] deno_npm::resolution::IncompleteSnapshotFromLockfileError, + ), + #[error(transparent)] + #[class(inherit)] + SnapshotFromLockfile(#[from] deno_npm::resolution::SnapshotFromLockfileError), +} + +async fn snapshot_from_lockfile( + lockfile: Arc, + api: &dyn NpmRegistryApi, +) -> Result { + let (incomplete_snapshot, skip_integrity_check) = { + let lock = lockfile.lock(); + ( + deno_npm::resolution::incomplete_snapshot_from_lockfile(&lock)?, + lock.overwrite, + ) + }; + let snapshot = deno_npm::resolution::snapshot_from_lockfile( + deno_npm::resolution::SnapshotFromLockfileParams { + incomplete_snapshot, + api, + skip_integrity_check, + }, + ) + .await?; + Ok(snapshot) +} + +/// An npm resolver where the resolution is managed by Deno rather than +/// the user bringing their own node_modules (BYONM) on the file system. +pub struct ManagedCliNpmResolver { + managed_npm_resolver: Arc>, + npm_cache_dir: Arc, + npm_rc: Arc, + sys: CliSys, + resolution: Arc, + system_info: NpmSystemInfo, +} + +impl std::fmt::Debug for ManagedCliNpmResolver { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("ManagedCliNpmResolver") + .field("", &"") + .finish() + } +} + +impl ManagedCliNpmResolver { + #[allow(clippy::too_many_arguments)] + pub fn new( + managed_npm_resolver: Arc>, + npm_cache_dir: Arc, + npm_rc: Arc, + resolution: Arc, + sys: CliSys, + system_info: NpmSystemInfo, + ) -> Self { + Self { + managed_npm_resolver, + npm_cache_dir, + npm_rc, + resolution, + sys, + system_info, + } + } + + pub fn resolve_pkg_folder_from_pkg_id( + &self, + pkg_id: &NpmPackageId, + ) -> Result { + self + .managed_npm_resolver + .resolve_pkg_folder_from_pkg_id(pkg_id) + } + + /// Resolves the package id from the provided specifier. + pub fn resolve_pkg_id_from_specifier( + &self, + specifier: &ModuleSpecifier, + ) -> Result, ResolvePkgIdFromSpecifierError> { + self + .managed_npm_resolver + .resolve_pkg_id_from_specifier(specifier) + } + + pub fn resolve_pkg_reqs_from_pkg_id( + &self, + id: &NpmPackageId, + ) -> Vec { + self.resolution.resolve_pkg_reqs_from_pkg_id(id) + } + + pub fn all_system_packages( + &self, + system_info: &NpmSystemInfo, + ) -> Vec { + self.resolution.all_system_packages(system_info) + } + + /// Checks if the provided package req's folder is cached. + pub fn is_pkg_req_folder_cached(&self, req: &PackageReq) -> bool { + self + .resolve_pkg_id_from_pkg_req(req) + .ok() + .and_then(|id| { + self + .managed_npm_resolver + .resolve_pkg_folder_from_pkg_id(&id) + .ok() + }) + .map(|folder| self.sys.fs_exists_no_err(folder)) + .unwrap_or(false) + } + + pub fn snapshot(&self) -> NpmResolutionSnapshot { + self.resolution.snapshot() + } + + pub fn top_package_req_for_name(&self, name: &str) -> Option { + let package_reqs = self.resolution.package_reqs(); + let mut entries = package_reqs + .iter() + .filter(|(_, nv)| nv.name == name) + .collect::>(); + entries.sort_by_key(|(_, nv)| &nv.version); + Some(entries.last()?.0.clone()) + } + + pub fn serialized_valid_snapshot_for_system( + &self, + system_info: &NpmSystemInfo, + ) -> ValidSerializedNpmResolutionSnapshot { + self + .resolution + .serialized_valid_snapshot_for_system(system_info) + } + + pub fn resolve_pkg_folder_from_deno_module( + &self, + nv: &PackageNv, + ) -> Result { + self + .managed_npm_resolver + .resolve_pkg_folder_from_deno_module(nv) + } + + pub fn resolve_pkg_id_from_pkg_req( + &self, + req: &PackageReq, + ) -> Result { + self.resolution.resolve_pkg_id_from_pkg_req(req) + } + + pub fn maybe_node_modules_path(&self) -> Option<&Path> { + self.managed_npm_resolver.node_modules_path() + } + + pub fn global_cache_root_path(&self) -> &Path { + self.npm_cache_dir.root_dir() + } + + pub fn global_cache_root_url(&self) -> &Url { + self.npm_cache_dir.root_dir_url() + } +} + +pub fn npm_process_state( + snapshot: ValidSerializedNpmResolutionSnapshot, + node_modules_path: Option<&Path>, +) -> String { + serde_json::to_string(&NpmProcessState { + kind: NpmProcessStateKind::Snapshot(snapshot.into_serialized()), + local_node_modules_path: node_modules_path + .map(|p| p.to_string_lossy().to_string()), + }) + .unwrap() +} + +impl NpmProcessStateProvider for ManagedCliNpmResolver { + fn get_npm_process_state(&self) -> String { + npm_process_state( + self.resolution.serialized_valid_snapshot(), + self.managed_npm_resolver.node_modules_path(), + ) + } +} + +impl CliNpmResolver for ManagedCliNpmResolver { + fn into_npm_pkg_folder_resolver( + self: Arc, + ) -> Arc { + self.managed_npm_resolver.clone() + } + + fn into_process_state_provider( + self: Arc, + ) -> Arc { + self + } + + fn into_byonm_or_managed( + self: Arc, + ) -> ByonmOrManagedNpmResolver { + ByonmOrManagedNpmResolver::Managed(self.managed_npm_resolver.clone()) + } + + fn clone_snapshotted(&self) -> Arc { + // create a new snapshotted npm resolution and resolver + let npm_resolution = + Arc::new(NpmResolutionCell::new(self.resolution.snapshot())); + + Arc::new(ManagedCliNpmResolver::new( + Arc::new(ManagedNpmResolver::::new::( + &self.npm_cache_dir, + &self.npm_rc, + npm_resolution.clone(), + self.sys.clone(), + self.root_node_modules_path().map(ToOwned::to_owned), + )), + self.npm_cache_dir.clone(), + self.npm_rc.clone(), + npm_resolution, + self.sys.clone(), + self.system_info.clone(), + )) + } + + fn as_inner(&self) -> InnerCliNpmResolverRef { + InnerCliNpmResolverRef::Managed(self) + } + + fn root_node_modules_path(&self) -> Option<&Path> { + self.managed_npm_resolver.node_modules_path() + } + + fn check_state_hash(&self) -> Option { + // We could go further and check all the individual + // npm packages, but that's probably overkill. + let mut package_reqs = self + .resolution + .package_reqs() + .into_iter() + .collect::>(); + package_reqs.sort_by(|a, b| a.0.cmp(&b.0)); // determinism + let mut hasher = FastInsecureHasher::new_without_deno_version(); + // ensure the cache gets busted when turning nodeModulesDir on or off + // as this could cause changes in resolution + hasher + .write_hashable(self.managed_npm_resolver.node_modules_path().is_some()); + for (pkg_req, pkg_nv) in package_reqs { + hasher.write_hashable(&pkg_req); + hasher.write_hashable(&pkg_nv); + } + Some(hasher.finish()) + } + + fn resolve_pkg_folder_from_deno_module_req( + &self, + req: &PackageReq, + referrer: &Url, + ) -> Result { + self + .managed_npm_resolver + .resolve_pkg_folder_from_deno_module_req(req, referrer) + .map_err(ResolvePkgFolderFromDenoReqError::Managed) + } +} diff --git a/cli/npm/managed/installers/mod.rs b/cli/npm/managed/installers/mod.rs deleted file mode 100644 index 39e0d6f77c..0000000000 --- a/cli/npm/managed/installers/mod.rs +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright 2018-2025 the Deno authors. MIT license. - -use std::path::PathBuf; -use std::sync::Arc; - -use deno_npm::NpmSystemInfo; -use deno_resolver::npm::managed::NpmResolution; - -pub use self::common::NpmPackageFsInstaller; -use self::global::GlobalNpmPackageInstaller; -use self::local::LocalNpmPackageInstaller; -use crate::args::LifecycleScriptsConfig; -use crate::args::NpmInstallDepsProvider; -use crate::npm::CliNpmCache; -use crate::npm::CliNpmTarballCache; -use crate::sys::CliSys; -use crate::util::progress_bar::ProgressBar; - -mod common; -mod global; -mod local; - -#[allow(clippy::too_many_arguments)] -pub fn create_npm_fs_installer( - npm_cache: Arc, - npm_install_deps_provider: &Arc, - progress_bar: &ProgressBar, - resolution: Arc, - sys: CliSys, - tarball_cache: Arc, - maybe_node_modules_path: Option, - system_info: NpmSystemInfo, - lifecycle_scripts: LifecycleScriptsConfig, -) -> Arc { - match maybe_node_modules_path { - Some(node_modules_folder) => Arc::new(LocalNpmPackageInstaller::new( - npm_cache, - npm_install_deps_provider.clone(), - progress_bar.clone(), - resolution, - sys, - tarball_cache, - node_modules_folder, - system_info, - lifecycle_scripts, - )), - None => Arc::new(GlobalNpmPackageInstaller::new( - npm_cache, - tarball_cache, - resolution, - system_info, - lifecycle_scripts, - )), - } -} diff --git a/cli/npm/managed/mod.rs b/cli/npm/managed/mod.rs deleted file mode 100644 index 8d2ede7e67..0000000000 --- a/cli/npm/managed/mod.rs +++ /dev/null @@ -1,768 +0,0 @@ -// Copyright 2018-2025 the Deno authors. MIT license. - -use std::borrow::Cow; -use std::path::Path; -use std::path::PathBuf; -use std::sync::Arc; - -use deno_ast::ModuleSpecifier; -use deno_cache_dir::npm::NpmCacheDir; -use deno_core::anyhow::Context; -use deno_core::error::AnyError; -use deno_core::serde_json; -use deno_core::url::Url; -use deno_error::JsErrorBox; -use deno_npm::npm_rc::ResolvedNpmRc; -use deno_npm::registry::NpmPackageInfo; -use deno_npm::registry::NpmRegistryApi; -use deno_npm::resolution::NpmResolutionSnapshot; -use deno_npm::resolution::PackageReqNotFoundError; -use deno_npm::resolution::ValidSerializedNpmResolutionSnapshot; -use deno_npm::NpmPackageId; -use deno_npm::NpmResolutionPackage; -use deno_npm::NpmSystemInfo; -use deno_npm_cache::NpmCacheSetting; -use deno_resolver::npm::managed::NpmResolution; -use deno_resolver::npm::managed::ResolvePkgFolderFromPkgIdError; -use deno_resolver::npm::ByonmOrManagedNpmResolver; -use deno_resolver::npm::ManagedNpmResolver; -use deno_resolver::npm::ResolvePkgFolderFromDenoReqError; -use deno_runtime::colors; -use deno_runtime::ops::process::NpmProcessStateProvider; -use deno_semver::package::PackageNv; -use deno_semver::package::PackageReq; -use installer::AddPkgReqsResult; -use installer::NpmResolutionInstaller; -use installers::create_npm_fs_installer; -use installers::NpmPackageFsInstaller; -use node_resolver::NpmPackageFolderResolver; - -use super::CliNpmCache; -use super::CliNpmCacheHttpClient; -use super::CliNpmRegistryInfoProvider; -use super::CliNpmResolver; -use super::CliNpmTarballCache; -use super::InnerCliNpmResolverRef; -use crate::args::CliLockfile; -use crate::args::LifecycleScriptsConfig; -use crate::args::NpmInstallDepsProvider; -use crate::args::NpmProcessState; -use crate::args::NpmProcessStateKind; -use crate::args::PackageJsonDepValueParseWithLocationError; -use crate::cache::FastInsecureHasher; -use crate::sys::CliSys; -use crate::util::progress_bar::ProgressBar; -use crate::util::sync::AtomicFlag; - -mod installer; -mod installers; - -pub enum CliNpmResolverManagedSnapshotOption { - ResolveFromLockfile(Arc), - Specified(Option), -} - -pub struct CliManagedNpmResolverCreateOptions { - pub snapshot: CliNpmResolverManagedSnapshotOption, - pub maybe_lockfile: Option>, - pub http_client_provider: Arc, - pub npm_cache_dir: Arc, - pub sys: CliSys, - pub cache_setting: deno_cache_dir::file_fetcher::CacheSetting, - pub text_only_progress_bar: crate::util::progress_bar::ProgressBar, - pub maybe_node_modules_path: Option, - pub npm_system_info: NpmSystemInfo, - pub npm_install_deps_provider: Arc, - pub npmrc: Arc, - pub lifecycle_scripts: LifecycleScriptsConfig, -} - -pub async fn create_managed_npm_resolver_for_lsp( - options: CliManagedNpmResolverCreateOptions, -) -> Arc { - let npm_cache = create_cache(&options); - let http_client = Arc::new(CliNpmCacheHttpClient::new( - options.http_client_provider.clone(), - options.text_only_progress_bar.clone(), - )); - let npm_api = create_api(npm_cache.clone(), http_client.clone(), &options); - // spawn due to the lsp's `Send` requirement - deno_core::unsync::spawn(async move { - let snapshot = match resolve_snapshot(&npm_api, options.snapshot).await { - Ok(snapshot) => snapshot, - Err(err) => { - log::warn!("failed to resolve snapshot: {}", err); - None - } - }; - create_inner( - http_client, - npm_cache, - options.npm_cache_dir, - options.npm_install_deps_provider, - npm_api, - options.sys, - options.text_only_progress_bar, - options.maybe_lockfile, - options.npmrc, - options.maybe_node_modules_path, - options.npm_system_info, - snapshot, - options.lifecycle_scripts, - ) - }) - .await - .unwrap() -} - -pub async fn create_managed_npm_resolver( - options: CliManagedNpmResolverCreateOptions, -) -> Result, AnyError> { - let npm_cache = create_cache(&options); - let http_client = Arc::new(CliNpmCacheHttpClient::new( - options.http_client_provider.clone(), - options.text_only_progress_bar.clone(), - )); - let api = create_api(npm_cache.clone(), http_client.clone(), &options); - let snapshot = resolve_snapshot(&api, options.snapshot).await?; - Ok(create_inner( - http_client, - npm_cache, - options.npm_cache_dir, - options.npm_install_deps_provider, - api, - options.sys, - options.text_only_progress_bar, - options.maybe_lockfile, - options.npmrc, - options.maybe_node_modules_path, - options.npm_system_info, - snapshot, - options.lifecycle_scripts, - )) -} - -#[allow(clippy::too_many_arguments)] -fn create_inner( - http_client: Arc, - npm_cache: Arc, - npm_cache_dir: Arc, - npm_install_deps_provider: Arc, - registry_info_provider: Arc, - sys: CliSys, - text_only_progress_bar: crate::util::progress_bar::ProgressBar, - maybe_lockfile: Option>, - npm_rc: Arc, - node_modules_dir_path: Option, - npm_system_info: NpmSystemInfo, - snapshot: Option, - lifecycle_scripts: LifecycleScriptsConfig, -) -> Arc { - let resolution = Arc::new(NpmResolution::from_serialized(snapshot)); - let tarball_cache = Arc::new(CliNpmTarballCache::new( - npm_cache.clone(), - http_client, - sys.clone(), - npm_rc.clone(), - )); - - let fs_installer = create_npm_fs_installer( - npm_cache.clone(), - &npm_install_deps_provider, - &text_only_progress_bar, - resolution.clone(), - sys.clone(), - tarball_cache.clone(), - node_modules_dir_path.clone(), - npm_system_info.clone(), - lifecycle_scripts.clone(), - ); - let managed_npm_resolver = - Arc::new(ManagedNpmResolver::::new::( - &npm_cache_dir, - &npm_rc, - resolution.clone(), - sys.clone(), - node_modules_dir_path, - )); - Arc::new(ManagedCliNpmResolver::new( - fs_installer, - maybe_lockfile, - managed_npm_resolver, - npm_cache, - npm_cache_dir, - npm_install_deps_provider, - npm_rc, - registry_info_provider, - resolution, - sys, - tarball_cache, - text_only_progress_bar, - npm_system_info, - lifecycle_scripts, - )) -} - -fn create_cache( - options: &CliManagedNpmResolverCreateOptions, -) -> Arc { - Arc::new(CliNpmCache::new( - options.npm_cache_dir.clone(), - options.sys.clone(), - NpmCacheSetting::from_cache_setting(&options.cache_setting), - options.npmrc.clone(), - )) -} - -fn create_api( - cache: Arc, - http_client: Arc, - options: &CliManagedNpmResolverCreateOptions, -) -> Arc { - Arc::new(CliNpmRegistryInfoProvider::new( - cache, - http_client, - options.npmrc.clone(), - )) -} - -async fn resolve_snapshot( - registry_info_provider: &Arc, - snapshot: CliNpmResolverManagedSnapshotOption, -) -> Result, AnyError> { - match snapshot { - CliNpmResolverManagedSnapshotOption::ResolveFromLockfile(lockfile) => { - if !lockfile.overwrite() { - let snapshot = snapshot_from_lockfile( - lockfile.clone(), - ®istry_info_provider.as_npm_registry_api(), - ) - .await - .with_context(|| { - format!("failed reading lockfile '{}'", lockfile.filename.display()) - })?; - Ok(Some(snapshot)) - } else { - Ok(None) - } - } - CliNpmResolverManagedSnapshotOption::Specified(snapshot) => Ok(snapshot), - } -} - -async fn snapshot_from_lockfile( - lockfile: Arc, - api: &dyn NpmRegistryApi, -) -> Result { - let (incomplete_snapshot, skip_integrity_check) = { - let lock = lockfile.lock(); - ( - deno_npm::resolution::incomplete_snapshot_from_lockfile(&lock)?, - lock.overwrite, - ) - }; - let snapshot = deno_npm::resolution::snapshot_from_lockfile( - deno_npm::resolution::SnapshotFromLockfileParams { - incomplete_snapshot, - api, - skip_integrity_check, - }, - ) - .await?; - Ok(snapshot) -} - -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum PackageCaching<'a> { - Only(Cow<'a, [PackageReq]>), - All, -} - -#[derive(Debug, thiserror::Error, deno_error::JsError)] -pub enum ResolvePkgFolderFromDenoModuleError { - #[class(inherit)] - #[error(transparent)] - PackageNvNotFound(#[from] deno_npm::resolution::PackageNvNotFoundError), - #[class(inherit)] - #[error(transparent)] - ResolvePkgFolderFromPkgId(#[from] ResolvePkgFolderFromPkgIdError), -} - -/// An npm resolver where the resolution is managed by Deno rather than -/// the user bringing their own node_modules (BYONM) on the file system. -pub struct ManagedCliNpmResolver { - fs_installer: Arc, - maybe_lockfile: Option>, - registry_info_provider: Arc, - managed_npm_resolver: Arc>, - npm_cache: Arc, - npm_cache_dir: Arc, - npm_install_deps_provider: Arc, - npm_rc: Arc, - sys: CliSys, - resolution: Arc, - resolution_installer: NpmResolutionInstaller, - tarball_cache: Arc, - text_only_progress_bar: ProgressBar, - npm_system_info: NpmSystemInfo, - top_level_install_flag: AtomicFlag, - lifecycle_scripts: LifecycleScriptsConfig, -} - -impl std::fmt::Debug for ManagedCliNpmResolver { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("ManagedCliNpmResolver") - .field("", &"") - .finish() - } -} - -impl ManagedCliNpmResolver { - #[allow(clippy::too_many_arguments)] - pub fn new( - fs_installer: Arc, - maybe_lockfile: Option>, - managed_npm_resolver: Arc>, - npm_cache: Arc, - npm_cache_dir: Arc, - npm_install_deps_provider: Arc, - npm_rc: Arc, - registry_info_provider: Arc, - resolution: Arc, - sys: CliSys, - tarball_cache: Arc, - text_only_progress_bar: ProgressBar, - npm_system_info: NpmSystemInfo, - lifecycle_scripts: LifecycleScriptsConfig, - ) -> Self { - let resolution_installer = NpmResolutionInstaller::new( - registry_info_provider.clone(), - resolution.clone(), - maybe_lockfile.clone(), - ); - Self { - fs_installer, - maybe_lockfile, - managed_npm_resolver, - npm_cache, - npm_cache_dir, - npm_install_deps_provider, - npm_rc, - registry_info_provider, - text_only_progress_bar, - resolution, - resolution_installer, - sys, - tarball_cache, - npm_system_info, - top_level_install_flag: Default::default(), - lifecycle_scripts, - } - } - - pub fn resolve_pkg_folder_from_pkg_id( - &self, - pkg_id: &NpmPackageId, - ) -> Result { - self - .managed_npm_resolver - .resolve_pkg_folder_from_pkg_id(pkg_id) - } - - /// Resolves the package id from the provided specifier. - pub fn resolve_pkg_id_from_specifier( - &self, - specifier: &ModuleSpecifier, - ) -> Result, AnyError> { - let Some(cache_folder_id) = self - .managed_npm_resolver - .resolve_package_cache_folder_id_from_specifier(specifier)? - else { - return Ok(None); - }; - Ok(Some( - self - .resolution - .resolve_pkg_id_from_pkg_cache_folder_id(&cache_folder_id)?, - )) - } - - pub fn resolve_pkg_reqs_from_pkg_id( - &self, - id: &NpmPackageId, - ) -> Vec { - self.resolution.resolve_pkg_reqs_from_pkg_id(id) - } - - /// Attempts to get the package size in bytes. - pub fn package_size( - &self, - package_id: &NpmPackageId, - ) -> Result { - let package_folder = self - .managed_npm_resolver - .resolve_pkg_folder_from_pkg_id(package_id)?; - Ok(crate::util::fs::dir_size(&package_folder)?) - } - - pub fn all_system_packages( - &self, - system_info: &NpmSystemInfo, - ) -> Vec { - self.resolution.all_system_packages(system_info) - } - - /// Checks if the provided package req's folder is cached. - pub fn is_pkg_req_folder_cached(&self, req: &PackageReq) -> bool { - self - .resolve_pkg_id_from_pkg_req(req) - .ok() - .and_then(|id| { - self - .managed_npm_resolver - .resolve_pkg_folder_from_pkg_id(&id) - .ok() - }) - .map(|folder| folder.exists()) - .unwrap_or(false) - } - - /// Adds package requirements to the resolver and ensures everything is setup. - /// This includes setting up the `node_modules` directory, if applicable. - pub async fn add_and_cache_package_reqs( - &self, - packages: &[PackageReq], - ) -> Result<(), JsErrorBox> { - self - .add_package_reqs_raw( - packages, - Some(PackageCaching::Only(packages.into())), - ) - .await - .dependencies_result - } - - pub async fn add_package_reqs_no_cache( - &self, - packages: &[PackageReq], - ) -> Result<(), JsErrorBox> { - self - .add_package_reqs_raw(packages, None) - .await - .dependencies_result - } - - pub async fn add_package_reqs( - &self, - packages: &[PackageReq], - caching: PackageCaching<'_>, - ) -> Result<(), JsErrorBox> { - self - .add_package_reqs_raw(packages, Some(caching)) - .await - .dependencies_result - } - - pub async fn add_package_reqs_raw<'a>( - &self, - packages: &[PackageReq], - caching: Option>, - ) -> AddPkgReqsResult { - if packages.is_empty() { - return AddPkgReqsResult { - dependencies_result: Ok(()), - results: vec![], - }; - } - - let mut result = self.resolution_installer.add_package_reqs(packages).await; - - if result.dependencies_result.is_ok() { - if let Some(lockfile) = self.maybe_lockfile.as_ref() { - result.dependencies_result = lockfile.error_if_changed(); - } - } - if result.dependencies_result.is_ok() { - if let Some(caching) = caching { - result.dependencies_result = self.cache_packages(caching).await; - } - } - - result - } - - /// Sets package requirements to the resolver, removing old requirements and adding new ones. - /// - /// This will retrieve and resolve package information, but not cache any package files. - pub async fn set_package_reqs( - &self, - packages: &[PackageReq], - ) -> Result<(), AnyError> { - self.resolution_installer.set_package_reqs(packages).await - } - - pub fn snapshot(&self) -> NpmResolutionSnapshot { - self.resolution.snapshot() - } - - pub fn top_package_req_for_name(&self, name: &str) -> Option { - let package_reqs = self.resolution.package_reqs(); - let mut entries = package_reqs - .iter() - .filter(|(_, nv)| nv.name == name) - .collect::>(); - entries.sort_by_key(|(_, nv)| &nv.version); - Some(entries.last()?.0.clone()) - } - - pub fn serialized_valid_snapshot_for_system( - &self, - system_info: &NpmSystemInfo, - ) -> ValidSerializedNpmResolutionSnapshot { - self - .resolution - .serialized_valid_snapshot_for_system(system_info) - } - - pub async fn inject_synthetic_types_node_package( - &self, - ) -> Result<(), JsErrorBox> { - let reqs = &[PackageReq::from_str("@types/node").unwrap()]; - // add and ensure this isn't added to the lockfile - self - .add_package_reqs(reqs, PackageCaching::Only(reqs.into())) - .await?; - - Ok(()) - } - - pub async fn cache_packages( - &self, - caching: PackageCaching<'_>, - ) -> Result<(), JsErrorBox> { - self.fs_installer.cache_packages(caching).await - } - - pub fn resolve_pkg_folder_from_deno_module( - &self, - nv: &PackageNv, - ) -> Result { - let pkg_id = self.resolution.resolve_pkg_id_from_deno_module(nv)?; - Ok(self.resolve_pkg_folder_from_pkg_id(&pkg_id)?) - } - - pub fn resolve_pkg_id_from_pkg_req( - &self, - req: &PackageReq, - ) -> Result { - self.resolution.resolve_pkg_id_from_pkg_req(req) - } - - pub fn ensure_no_pkg_json_dep_errors( - &self, - ) -> Result<(), Box> { - for err in self.npm_install_deps_provider.pkg_json_dep_errors() { - match err.source.as_kind() { - deno_package_json::PackageJsonDepValueParseErrorKind::VersionReq(_) => { - return Err(Box::new(err.clone())); - } - deno_package_json::PackageJsonDepValueParseErrorKind::Unsupported { - .. - } => { - // only warn for this one - log::warn!( - "{} {}\n at {}", - colors::yellow("Warning"), - err.source, - err.location, - ) - } - } - } - Ok(()) - } - - /// Ensures that the top level `package.json` dependencies are installed. - /// This may set up the `node_modules` directory. - /// - /// Returns `true` if the top level packages are already installed. A - /// return value of `false` means that new packages were added to the NPM resolution. - pub async fn ensure_top_level_package_json_install( - &self, - ) -> Result { - if !self.top_level_install_flag.raise() { - return Ok(true); // already did this - } - - let pkg_json_remote_pkgs = self.npm_install_deps_provider.remote_pkgs(); - if pkg_json_remote_pkgs.is_empty() { - return Ok(true); - } - - // check if something needs resolving before bothering to load all - // the package information (which is slow) - if pkg_json_remote_pkgs.iter().all(|pkg| { - self - .resolution - .resolve_pkg_id_from_pkg_req(&pkg.req) - .is_ok() - }) { - log::debug!( - "All package.json deps resolvable. Skipping top level install." - ); - return Ok(true); // everything is already resolvable - } - - let pkg_reqs = pkg_json_remote_pkgs - .iter() - .map(|pkg| pkg.req.clone()) - .collect::>(); - self.add_package_reqs_no_cache(&pkg_reqs).await?; - - Ok(false) - } - - pub async fn cache_package_info( - &self, - package_name: &str, - ) -> Result, AnyError> { - // this will internally cache the package information - self - .registry_info_provider - .package_info(package_name) - .await - .map_err(|err| err.into()) - } - - pub fn maybe_node_modules_path(&self) -> Option<&Path> { - self.managed_npm_resolver.node_modules_path() - } - - pub fn global_cache_root_path(&self) -> &Path { - self.npm_cache_dir.root_dir() - } - - pub fn global_cache_root_url(&self) -> &Url { - self.npm_cache_dir.root_dir_url() - } -} - -fn npm_process_state( - snapshot: ValidSerializedNpmResolutionSnapshot, - node_modules_path: Option<&Path>, -) -> String { - serde_json::to_string(&NpmProcessState { - kind: NpmProcessStateKind::Snapshot(snapshot.into_serialized()), - local_node_modules_path: node_modules_path - .map(|p| p.to_string_lossy().to_string()), - }) - .unwrap() -} - -impl NpmProcessStateProvider for ManagedCliNpmResolver { - fn get_npm_process_state(&self) -> String { - npm_process_state( - self.resolution.serialized_valid_snapshot(), - self.managed_npm_resolver.node_modules_path(), - ) - } -} - -impl CliNpmResolver for ManagedCliNpmResolver { - fn into_npm_pkg_folder_resolver( - self: Arc, - ) -> Arc { - self.managed_npm_resolver.clone() - } - - fn into_process_state_provider( - self: Arc, - ) -> Arc { - self - } - - fn into_byonm_or_managed( - self: Arc, - ) -> ByonmOrManagedNpmResolver { - ByonmOrManagedNpmResolver::Managed(self.managed_npm_resolver.clone()) - } - - fn clone_snapshotted(&self) -> Arc { - // create a new snapshotted npm resolution and resolver - let npm_resolution = - Arc::new(NpmResolution::new(self.resolution.snapshot())); - - Arc::new(ManagedCliNpmResolver::new( - create_npm_fs_installer( - self.npm_cache.clone(), - &self.npm_install_deps_provider, - &self.text_only_progress_bar, - npm_resolution.clone(), - self.sys.clone(), - self.tarball_cache.clone(), - self.root_node_modules_path().map(ToOwned::to_owned), - self.npm_system_info.clone(), - self.lifecycle_scripts.clone(), - ), - self.maybe_lockfile.clone(), - Arc::new(ManagedNpmResolver::::new::( - &self.npm_cache_dir, - &self.npm_rc, - npm_resolution.clone(), - self.sys.clone(), - self.root_node_modules_path().map(ToOwned::to_owned), - )), - self.npm_cache.clone(), - self.npm_cache_dir.clone(), - self.npm_install_deps_provider.clone(), - self.npm_rc.clone(), - self.registry_info_provider.clone(), - npm_resolution, - self.sys.clone(), - self.tarball_cache.clone(), - self.text_only_progress_bar.clone(), - self.npm_system_info.clone(), - self.lifecycle_scripts.clone(), - )) - } - - fn as_inner(&self) -> InnerCliNpmResolverRef { - InnerCliNpmResolverRef::Managed(self) - } - - fn root_node_modules_path(&self) -> Option<&Path> { - self.managed_npm_resolver.node_modules_path() - } - - fn check_state_hash(&self) -> Option { - // We could go further and check all the individual - // npm packages, but that's probably overkill. - let mut package_reqs = self - .resolution - .package_reqs() - .into_iter() - .collect::>(); - package_reqs.sort_by(|a, b| a.0.cmp(&b.0)); // determinism - let mut hasher = FastInsecureHasher::new_without_deno_version(); - // ensure the cache gets busted when turning nodeModulesDir on or off - // as this could cause changes in resolution - hasher - .write_hashable(self.managed_npm_resolver.node_modules_path().is_some()); - for (pkg_req, pkg_nv) in package_reqs { - hasher.write_hashable(&pkg_req); - hasher.write_hashable(&pkg_nv); - } - Some(hasher.finish()) - } - - fn resolve_pkg_folder_from_deno_module_req( - &self, - req: &PackageReq, - referrer: &Url, - ) -> Result { - self - .managed_npm_resolver - .resolve_pkg_folder_from_deno_module_req(req, referrer) - .map_err(ResolvePkgFolderFromDenoReqError::Managed) - } -} diff --git a/cli/npm/mod.rs b/cli/npm/mod.rs index 052a98e6cf..6ad7ad610e 100644 --- a/cli/npm/mod.rs +++ b/cli/npm/mod.rs @@ -1,6 +1,7 @@ // Copyright 2018-2025 the Deno authors. MIT license. mod byonm; +pub mod installer; mod managed; mod permission_checker; @@ -9,7 +10,6 @@ use std::path::PathBuf; use std::sync::Arc; use dashmap::DashMap; -use deno_core::error::AnyError; use deno_core::serde_json; use deno_core::url::Url; use deno_error::JsErrorBox; @@ -30,8 +30,8 @@ pub use self::byonm::CliByonmNpmResolverCreateOptions; pub use self::managed::CliManagedNpmResolverCreateOptions; pub use self::managed::CliNpmResolverManagedSnapshotOption; pub use self::managed::ManagedCliNpmResolver; -pub use self::managed::PackageCaching; -pub use self::managed::ResolvePkgFolderFromDenoModuleError; +pub use self::managed::NpmResolutionInitializer; +pub use self::managed::ResolveSnapshotError; pub use self::permission_checker::NpmRegistryReadPermissionChecker; pub use self::permission_checker::NpmRegistryReadPermissionCheckerMode; use crate::file_fetcher::CliFileFetcher; @@ -109,28 +109,16 @@ pub enum CliNpmResolverCreateOptions { Byonm(CliByonmNpmResolverCreateOptions), } -pub async fn create_cli_npm_resolver_for_lsp( +pub fn create_cli_npm_resolver( options: CliNpmResolverCreateOptions, ) -> Arc { use CliNpmResolverCreateOptions::*; match options { - Managed(options) => { - managed::create_managed_npm_resolver_for_lsp(options).await - } + Managed(options) => managed::create_managed_npm_resolver(options), Byonm(options) => Arc::new(ByonmNpmResolver::new(options)), } } -pub async fn create_cli_npm_resolver( - options: CliNpmResolverCreateOptions, -) -> Result, AnyError> { - use CliNpmResolverCreateOptions::*; - match options { - Managed(options) => managed::create_managed_npm_resolver(options).await, - Byonm(options) => Ok(Arc::new(ByonmNpmResolver::new(options))), - } -} - pub enum InnerCliNpmResolverRef<'a> { Managed(&'a ManagedCliNpmResolver), #[allow(dead_code)] diff --git a/cli/resolver.rs b/cli/resolver.rs index 1d12d5f8b7..2f3d42e9e1 100644 --- a/cli/resolver.rs +++ b/cli/resolver.rs @@ -33,8 +33,8 @@ use thiserror::Error; use crate::args::NpmCachingStrategy; use crate::args::DENO_DISABLE_PEDANTIC_NODE_WARNINGS; use crate::node::CliNodeCodeTranslator; -use crate::npm::CliNpmResolver; -use crate::npm::InnerCliNpmResolverRef; +use crate::npm::installer::NpmInstaller; +use crate::npm::installer::PackageCaching; use crate::sys::CliSys; use crate::util::sync::AtomicFlag; use crate::util::text_encoding::from_utf8_lossy_cow; @@ -164,48 +164,30 @@ impl NpmModuleLoader { } } -pub struct CliResolverOptions { - pub deno_resolver: Arc, - pub npm_resolver: Option>, - pub bare_node_builtins_enabled: bool, -} +#[derive(Debug, Default)] +pub struct FoundPackageJsonDepFlag(AtomicFlag); /// A resolver that takes care of resolution, taking into account loaded /// import map, JSX settings. #[derive(Debug)] pub struct CliResolver { deno_resolver: Arc, - npm_resolver: Option>, - found_package_json_dep_flag: AtomicFlag, - bare_node_builtins_enabled: bool, + found_package_json_dep_flag: Arc, warned_pkgs: DashSet, } impl CliResolver { - pub fn new(options: CliResolverOptions) -> Self { + pub fn new( + deno_resolver: Arc, + found_package_json_dep_flag: Arc, + ) -> Self { Self { - deno_resolver: options.deno_resolver, - npm_resolver: options.npm_resolver, - found_package_json_dep_flag: Default::default(), - bare_node_builtins_enabled: options.bare_node_builtins_enabled, + deno_resolver, + found_package_json_dep_flag, warned_pkgs: Default::default(), } } - // todo(dsherret): move this off CliResolver as CliResolver is acting - // like a factory by doing this (it's beyond its responsibility) - pub fn create_graph_npm_resolver( - &self, - npm_caching: NpmCachingStrategy, - ) -> WorkerCliNpmGraphResolver { - WorkerCliNpmGraphResolver { - npm_resolver: self.npm_resolver.as_ref(), - found_package_json_dep_flag: &self.found_package_json_dep_flag, - bare_node_builtins_enabled: self.bare_node_builtins_enabled, - npm_caching, - } - } - pub fn resolve( &self, raw_specifier: &str, @@ -233,7 +215,7 @@ impl CliResolver { if resolution.found_package_json_dep { // mark that we need to do an "npm install" later - self.found_package_json_dep_flag.raise(); + self.found_package_json_dep_flag.0.raise(); } if let Some(diagnostic) = resolution.maybe_diagnostic { @@ -260,15 +242,31 @@ impl CliResolver { } #[derive(Debug)] -pub struct WorkerCliNpmGraphResolver<'a> { - npm_resolver: Option<&'a Arc>, - found_package_json_dep_flag: &'a AtomicFlag, +pub struct CliNpmGraphResolver { + npm_installer: Option>, + found_package_json_dep_flag: Arc, bare_node_builtins_enabled: bool, npm_caching: NpmCachingStrategy, } +impl CliNpmGraphResolver { + pub fn new( + npm_installer: Option>, + found_package_json_dep_flag: Arc, + bare_node_builtins_enabled: bool, + npm_caching: NpmCachingStrategy, + ) -> Self { + Self { + npm_installer, + found_package_json_dep_flag, + bare_node_builtins_enabled, + npm_caching, + } + } +} + #[async_trait(?Send)] -impl<'a> deno_graph::source::NpmResolver for WorkerCliNpmGraphResolver<'a> { +impl deno_graph::source::NpmResolver for CliNpmGraphResolver { fn resolve_builtin_node_module( &self, specifier: &ModuleSpecifier, @@ -298,17 +296,12 @@ impl<'a> deno_graph::source::NpmResolver for WorkerCliNpmGraphResolver<'a> { } fn load_and_cache_npm_package_info(&self, package_name: &str) { - match self.npm_resolver { - Some(npm_resolver) if npm_resolver.as_managed().is_some() => { - let npm_resolver = npm_resolver.clone(); - let package_name = package_name.to_string(); - deno_core::unsync::spawn(async move { - if let Some(managed) = npm_resolver.as_managed() { - let _ignore = managed.cache_package_info(&package_name).await; - } - }); - } - _ => {} + if let Some(npm_installer) = &self.npm_installer { + let npm_installer = npm_installer.clone(); + let package_name = package_name.to_string(); + deno_core::unsync::spawn(async move { + let _ignore = npm_installer.cache_package_info(&package_name).await; + }); } } @@ -316,17 +309,11 @@ impl<'a> deno_graph::source::NpmResolver for WorkerCliNpmGraphResolver<'a> { &self, package_reqs: &[PackageReq], ) -> NpmResolvePkgReqsResult { - match &self.npm_resolver { - Some(npm_resolver) => { - let npm_resolver = match npm_resolver.as_inner() { - InnerCliNpmResolverRef::Managed(npm_resolver) => npm_resolver, - // if we are using byonm, then this should never be called because - // we don't use deno_graph's npm resolution in this case - InnerCliNpmResolverRef::Byonm(_) => unreachable!(), - }; - - let top_level_result = if self.found_package_json_dep_flag.is_raised() { - npm_resolver + match &self.npm_installer { + Some(npm_installer) => { + let top_level_result = if self.found_package_json_dep_flag.0.is_raised() + { + npm_installer .ensure_top_level_package_json_install() .await .map(|_| ()) @@ -334,15 +321,13 @@ impl<'a> deno_graph::source::NpmResolver for WorkerCliNpmGraphResolver<'a> { Ok(()) }; - let result = npm_resolver + let result = npm_installer .add_package_reqs_raw( package_reqs, match self.npm_caching { - NpmCachingStrategy::Eager => { - Some(crate::npm::PackageCaching::All) - } + NpmCachingStrategy::Eager => Some(PackageCaching::All), NpmCachingStrategy::Lazy => { - Some(crate::npm::PackageCaching::Only(package_reqs.into())) + Some(PackageCaching::Only(package_reqs.into())) } NpmCachingStrategy::Manual => None, }, diff --git a/cli/standalone/mod.rs b/cli/standalone/mod.rs index ff38912a89..4ca82f6b7d 100644 --- a/cli/standalone/mod.rs +++ b/cli/standalone/mod.rs @@ -37,10 +37,12 @@ use deno_core::ResolutionKind; use deno_core::SourceCodeCacheInfo; use deno_error::JsErrorBox; use deno_npm::npm_rc::ResolvedNpmRc; +use deno_npm::resolution::NpmResolutionSnapshot; use deno_package_json::PackageJsonDepValue; use deno_resolver::cjs::IsCjsResolutionMode; use deno_resolver::npm::create_in_npm_pkg_checker; use deno_resolver::npm::managed::ManagedInNpmPkgCheckerCreateOptions; +use deno_resolver::npm::managed::NpmResolutionCell; use deno_resolver::npm::CreateInNpmPkgCheckerOptions; use deno_resolver::npm::NpmReqResolverOptions; use deno_runtime::deno_fs; @@ -91,6 +93,7 @@ use crate::npm::CliNpmResolverCreateOptions; use crate::npm::CliNpmResolverManagedSnapshotOption; use crate::npm::NpmRegistryReadPermissionChecker; use crate::npm::NpmRegistryReadPermissionCheckerMode; +use crate::npm::NpmResolutionInitializer; use crate::resolver::CjsTracker; use crate::resolver::CliNpmReqResolver; use crate::resolver::NpmModuleLoader; @@ -687,18 +690,12 @@ pub async fn run( ca_data: metadata.ca_data.map(CaData::Bytes), cell: Default::default(), }); - let progress_bar = ProgressBar::new(ProgressBarStyle::TextOnly); - let http_client_provider = Arc::new(HttpClientProvider::new( - Some(root_cert_store_provider.clone()), - metadata.unsafely_ignore_certificate_errors.clone(), - )); // use a dummy npm registry url let npm_registry_url = ModuleSpecifier::parse("https://localhost/").unwrap(); let root_dir_url = Arc::new(ModuleSpecifier::from_directory_path(&root_path).unwrap()); let main_module = root_dir_url.join(&metadata.entrypoint_key).unwrap(); let npm_global_cache_dir = root_path.join(".deno_compile_node_modules"); - let cache_setting = CacheSetting::Only; let pkg_json_resolver = Arc::new(CliPackageJsonResolver::new(sys.clone())); let npm_registry_permission_checker = { let mode = match &metadata.node_modules { @@ -743,29 +740,19 @@ pub async fn run( maybe_node_modules_path: maybe_node_modules_path.as_deref(), }, )); + let npm_resolution = + Arc::new(NpmResolutionCell::new(NpmResolutionSnapshot::new(snapshot))); let npm_resolver = create_cli_npm_resolver(CliNpmResolverCreateOptions::Managed( CliManagedNpmResolverCreateOptions { - snapshot: CliNpmResolverManagedSnapshotOption::Specified(Some( - snapshot, - )), - maybe_lockfile: None, - http_client_provider: http_client_provider.clone(), + npm_resolution, npm_cache_dir, - npm_install_deps_provider: Arc::new( - // this is only used for installing packages, which isn't necessary with deno compile - NpmInstallDepsProvider::empty(), - ), sys: sys.clone(), - text_only_progress_bar: progress_bar, - cache_setting, maybe_node_modules_path, npm_system_info: Default::default(), npmrc, - lifecycle_scripts: Default::default(), }, - )) - .await?; + )); (in_npm_pkg_checker, npm_resolver) } Some(binary::NodeModules::Byonm { @@ -781,8 +768,7 @@ pub async fn run( pkg_json_resolver: pkg_json_resolver.clone(), root_node_modules_dir, }), - ) - .await?; + ); (in_npm_pkg_checker, npm_resolver) } None => { @@ -801,27 +787,18 @@ pub async fn run( maybe_node_modules_path: None, }, )); + let npm_resolution = Arc::new(NpmResolutionCell::default()); let npm_resolver = create_cli_npm_resolver(CliNpmResolverCreateOptions::Managed( CliManagedNpmResolverCreateOptions { - snapshot: CliNpmResolverManagedSnapshotOption::Specified(None), - http_client_provider: http_client_provider.clone(), - npm_install_deps_provider: Arc::new( - // this is only used for installing packages, which isn't necessary with deno compile - NpmInstallDepsProvider::empty(), - ), + npm_resolution, sys: sys.clone(), - cache_setting, - text_only_progress_bar: progress_bar, npm_cache_dir, - maybe_lockfile: None, maybe_node_modules_path: None, npm_system_info: Default::default(), npmrc: create_default_npmrc(), - lifecycle_scripts: Default::default(), }, - )) - .await?; + )); (in_npm_pkg_checker, npm_resolver) } }; @@ -995,6 +972,7 @@ pub async fn run( None, Box::new(module_loader_factory), node_resolver, + None, npm_resolver, pkg_json_resolver, root_cert_store_provider, diff --git a/cli/tools/check.rs b/cli/tools/check.rs index 53fd5c5db9..c3a285a9b2 100644 --- a/cli/tools/check.rs +++ b/cli/tools/check.rs @@ -34,6 +34,7 @@ use crate::graph_util::maybe_additional_sloppy_imports_message; use crate::graph_util::BuildFastCheckGraphOptions; use crate::graph_util::ModuleGraphBuilder; use crate::node::CliNodeResolver; +use crate::npm::installer::NpmInstaller; use crate::npm::CliNpmResolver; use crate::sys::CliSys; use crate::tsc; @@ -109,6 +110,7 @@ pub struct TypeChecker { cjs_tracker: Arc, cli_options: Arc, module_graph_builder: Arc, + npm_installer: Option>, node_resolver: Arc, npm_resolver: Arc, sys: CliSys, @@ -136,12 +138,14 @@ pub enum CheckError { } impl TypeChecker { + #[allow(clippy::too_many_arguments)] pub fn new( caches: Arc, cjs_tracker: Arc, cli_options: Arc, module_graph_builder: Arc, node_resolver: Arc, + npm_installer: Option>, npm_resolver: Arc, sys: CliSys, ) -> Self { @@ -151,6 +155,7 @@ impl TypeChecker { cli_options, module_graph_builder, node_resolver, + npm_installer, npm_resolver, sys, } @@ -191,9 +196,9 @@ impl TypeChecker { // node built-in specifiers use the @types/node package to determine // types, so inject that now (the caller should do this after the lockfile // has been written) - if let Some(npm_resolver) = self.npm_resolver.as_managed() { + if let Some(npm_installer) = &self.npm_installer { if graph.has_node_specifier { - npm_resolver.inject_synthetic_types_node_package().await?; + npm_installer.inject_synthetic_types_node_package().await?; } } diff --git a/cli/tools/info.rs b/cli/tools/info.rs index 81b0af0a66..50faeda7d3 100644 --- a/cli/tools/info.rs +++ b/cli/tools/info.rs @@ -386,8 +386,11 @@ impl NpmInfo { npm_snapshot: &'a NpmResolutionSnapshot, ) { self.packages.insert(package.id.clone(), package.clone()); - if let Ok(size) = npm_resolver.package_size(&package.id) { - self.package_sizes.insert(package.id.clone(), size); + if let Ok(folder) = npm_resolver.resolve_pkg_folder_from_pkg_id(&package.id) + { + if let Ok(size) = crate::util::fs::dir_size(&folder) { + self.package_sizes.insert(package.id.clone(), size); + } } for id in package.dependencies.values() { if !self.packages.contains_key(id) { diff --git a/cli/tools/installer.rs b/cli/tools/installer.rs index ff5744c07a..596d087589 100644 --- a/cli/tools/installer.rs +++ b/cli/tools/installer.rs @@ -300,8 +300,8 @@ async fn install_local( InstallFlagsLocal::TopLevel => { let factory = CliFactory::from_flags(flags); // surface any errors in the package.json - if let Some(npm_resolver) = factory.npm_resolver().await?.as_managed() { - npm_resolver.ensure_no_pkg_json_dep_errors()?; + if let Some(npm_installer) = factory.npm_installer_if_managed()? { + npm_installer.ensure_no_pkg_json_dep_errors()?; } crate::tools::registry::cache_top_level_deps(&factory, None).await?; diff --git a/cli/tools/jupyter/mod.rs b/cli/tools/jupyter/mod.rs index 78b7675420..67c604118c 100644 --- a/cli/tools/jupyter/mod.rs +++ b/cli/tools/jupyter/mod.rs @@ -67,7 +67,7 @@ pub async fn kernel( // TODO(bartlomieju): should we run with all permissions? let permissions = PermissionsContainer::allow_all(factory.permission_desc_parser()?.clone()); - let npm_resolver = factory.npm_resolver().await?.clone(); + let npm_installer = factory.npm_installer_if_managed()?.cloned(); let resolver = factory.resolver().await?.clone(); let worker_factory = factory.create_cli_main_worker_factory().await?; let (stdio_tx, stdio_rx) = mpsc::unbounded_channel(); @@ -115,7 +115,7 @@ pub async fn kernel( let worker = worker.into_main_worker(); let mut repl_session = repl::ReplSession::initialize( cli_options, - npm_resolver, + npm_installer, resolver, worker, main_module, diff --git a/cli/tools/registry/pm.rs b/cli/tools/registry/pm.rs index 2b1266bafb..0b27b1a3e3 100644 --- a/cli/tools/registry/pm.rs +++ b/cli/tools/registry/pm.rs @@ -861,10 +861,8 @@ async fn npm_install_after_modification( // make a new CliFactory to pick up the updated config file let cli_factory = CliFactory::from_flags(flags); // surface any errors in the package.json - let npm_resolver = cli_factory.npm_resolver().await?; - if let Some(npm_resolver) = npm_resolver.as_managed() { - npm_resolver.ensure_no_pkg_json_dep_errors()?; - } + let npm_installer = cli_factory.npm_installer()?; + npm_installer.ensure_no_pkg_json_dep_errors()?; // npm install cache_deps::cache_top_level_deps(&cli_factory, jsr_resolver).await?; diff --git a/cli/tools/registry/pm/cache_deps.rs b/cli/tools/registry/pm/cache_deps.rs index 3137939cea..5683a30cc8 100644 --- a/cli/tools/registry/pm/cache_deps.rs +++ b/cli/tools/registry/pm/cache_deps.rs @@ -12,16 +12,19 @@ use crate::factory::CliFactory; use crate::graph_container::ModuleGraphContainer; use crate::graph_container::ModuleGraphUpdatePermit; use crate::graph_util::CreateGraphOptions; +use crate::npm::installer::PackageCaching; pub async fn cache_top_level_deps( // todo(dsherret): don't pass the factory into this function. Instead use ctor deps factory: &CliFactory, jsr_resolver: Option>, ) -> Result<(), AnyError> { - let npm_resolver = factory.npm_resolver().await?; + let npm_installer = factory.npm_installer_if_managed()?; let cli_options = factory.cli_options()?; - if let Some(npm_resolver) = npm_resolver.as_managed() { - npm_resolver.ensure_top_level_package_json_install().await?; + if let Some(npm_installer) = &npm_installer { + npm_installer + .ensure_top_level_package_json_install() + .await?; if let Some(lockfile) = cli_options.maybe_lockfile() { lockfile.error_if_changed()?; } @@ -138,10 +141,8 @@ pub async fn cache_top_level_deps( maybe_graph_error = graph_builder.graph_roots_valid(graph, &roots); } - if let Some(npm_resolver) = npm_resolver.as_managed() { - npm_resolver - .cache_packages(crate::npm::PackageCaching::All) - .await?; + if let Some(npm_installer) = &npm_installer { + npm_installer.cache_packages(PackageCaching::All).await?; } maybe_graph_error?; diff --git a/cli/tools/registry/pm/deps.rs b/cli/tools/registry/pm/deps.rs index 708f95f987..1dbb464dbe 100644 --- a/cli/tools/registry/pm/deps.rs +++ b/cli/tools/registry/pm/deps.rs @@ -42,6 +42,7 @@ use crate::graph_container::ModuleGraphContainer; use crate::graph_container::ModuleGraphUpdatePermit; use crate::jsr::JsrFetchResolver; use crate::module_loader::ModuleLoadPreparer; +use crate::npm::installer::NpmInstaller; use crate::npm::CliNpmResolver; use crate::npm::NpmFetchResolver; use crate::util::sync::AtomicFlag; @@ -451,6 +452,7 @@ pub struct DepManager { pub(crate) jsr_fetch_resolver: Arc, pub(crate) npm_fetch_resolver: Arc, npm_resolver: Arc, + npm_installer: Arc, permissions_container: PermissionsContainer, main_module_graph_container: Arc, lockfile: Option>, @@ -460,6 +462,7 @@ pub struct DepManagerArgs { pub module_load_preparer: Arc, pub jsr_fetch_resolver: Arc, pub npm_fetch_resolver: Arc, + pub npm_installer: Arc, pub npm_resolver: Arc, pub permissions_container: PermissionsContainer, pub main_module_graph_container: Arc, @@ -477,6 +480,7 @@ impl DepManager { module_load_preparer, jsr_fetch_resolver, npm_fetch_resolver, + npm_installer, npm_resolver, permissions_container, main_module_graph_container, @@ -490,6 +494,7 @@ impl DepManager { dependencies_resolved: AtomicFlag::lowered(), module_load_preparer, npm_fetch_resolver, + npm_installer, npm_resolver, permissions_container, main_module_graph_container, @@ -556,7 +561,10 @@ impl DepManager { return Ok(()); } - npm_resolver.ensure_top_level_package_json_install().await?; + self + .npm_installer + .ensure_top_level_package_json_install() + .await?; let mut roots = Vec::new(); let mut info_futures = FuturesUnordered::new(); for dep in &self.deps { diff --git a/cli/tools/registry/pm/outdated.rs b/cli/tools/registry/pm/outdated.rs index e7fc88c31f..939c30b5c1 100644 --- a/cli/tools/registry/pm/outdated.rs +++ b/cli/tools/registry/pm/outdated.rs @@ -445,6 +445,7 @@ async fn dep_manager_args( jsr_fetch_resolver, npm_fetch_resolver, npm_resolver: factory.npm_resolver().await?.clone(), + npm_installer: factory.npm_installer()?.clone(), permissions_container: factory.root_permissions_container()?.clone(), main_module_graph_container: factory .main_module_graph_container() diff --git a/cli/tools/repl/mod.rs b/cli/tools/repl/mod.rs index 1bc6a2f9ea..05e2f320eb 100644 --- a/cli/tools/repl/mod.rs +++ b/cli/tools/repl/mod.rs @@ -164,7 +164,7 @@ pub async fn run( let cli_options = factory.cli_options()?; let main_module = cli_options.resolve_main_module()?; let permissions = factory.root_permissions_container()?; - let npm_resolver = factory.npm_resolver().await?.clone(); + let npm_installer = factory.npm_installer_if_managed()?.cloned(); let resolver = factory.resolver().await?.clone(); let file_fetcher = factory.file_fetcher()?; let worker_factory = factory.create_cli_main_worker_factory().await?; @@ -187,7 +187,7 @@ pub async fn run( let worker = worker.into_main_worker(); let session = ReplSession::initialize( cli_options, - npm_resolver, + npm_installer, resolver, worker, main_module.clone(), diff --git a/cli/tools/repl/session.rs b/cli/tools/repl/session.rs index ed9dd61e2d..5ea4523c48 100644 --- a/cli/tools/repl/session.rs +++ b/cli/tools/repl/session.rs @@ -45,7 +45,7 @@ use crate::args::CliOptions; use crate::cdp; use crate::colors; use crate::lsp::ReplLanguageServer; -use crate::npm::CliNpmResolver; +use crate::npm::installer::NpmInstaller; use crate::resolver::CliResolver; use crate::tools::test::report_tests; use crate::tools::test::reporters::PrettyTestReporter; @@ -181,7 +181,7 @@ struct ReplJsxState { } pub struct ReplSession { - npm_resolver: Arc, + npm_installer: Option>, resolver: Arc, pub worker: MainWorker, session: LocalInspectorSession, @@ -200,7 +200,7 @@ pub struct ReplSession { impl ReplSession { pub async fn initialize( cli_options: &CliOptions, - npm_resolver: Arc, + npm_installer: Option>, resolver: Arc, mut worker: MainWorker, main_module: ModuleSpecifier, @@ -265,7 +265,7 @@ impl ReplSession { )?; let experimental_decorators = transpile_options.use_ts_decorators; let mut repl_session = ReplSession { - npm_resolver, + npm_installer, resolver, worker, session, @@ -704,8 +704,8 @@ impl ReplSession { &mut self, program: &swc_ast::Program, ) -> Result<(), AnyError> { - let Some(npm_resolver) = self.npm_resolver.as_managed() else { - return Ok(()); // don't auto-install for byonm + let Some(npm_installer) = &self.npm_installer else { + return Ok(()); }; let mut collector = ImportCollector::new(); @@ -737,13 +737,13 @@ impl ReplSession { let has_node_specifier = resolved_imports.iter().any(|url| url.scheme() == "node"); if !npm_imports.is_empty() || has_node_specifier { - npm_resolver + npm_installer .add_and_cache_package_reqs(&npm_imports) .await?; // prevent messages in the repl about @types/node not being cached if has_node_specifier { - npm_resolver.inject_synthetic_types_node_package().await?; + npm_installer.inject_synthetic_types_node_package().await?; } } Ok(()) diff --git a/cli/tools/run/mod.rs b/cli/tools/run/mod.rs index a10ce32947..ecf1bd52f3 100644 --- a/cli/tools/run/mod.rs +++ b/cli/tools/run/mod.rs @@ -12,6 +12,7 @@ use crate::args::EvalFlags; use crate::args::Flags; use crate::args::WatchFlagsWithPaths; use crate::factory::CliFactory; +use crate::npm::installer::PackageCaching; use crate::util; use crate::util::file_watcher::WatcherRestartMode; @@ -202,18 +203,17 @@ pub async fn maybe_npm_install(factory: &CliFactory) -> Result<(), AnyError> { // ensure an "npm install" is done if the user has explicitly // opted into using a managed node_modules directory if cli_options.node_modules_dir()? == Some(NodeModulesDirMode::Auto) { - if let Some(npm_resolver) = factory.npm_resolver().await?.as_managed() { - let already_done = - npm_resolver.ensure_top_level_package_json_install().await?; + if let Some(npm_installer) = factory.npm_installer_if_managed()? { + let already_done = npm_installer + .ensure_top_level_package_json_install() + .await?; if !already_done && matches!( cli_options.default_npm_caching_strategy(), crate::graph_util::NpmCachingStrategy::Eager ) { - npm_resolver - .cache_packages(crate::npm::PackageCaching::All) - .await?; + npm_installer.cache_packages(PackageCaching::All).await?; } } } diff --git a/cli/tools/task.rs b/cli/tools/task.rs index 85834a0bfc..d6d87dd45e 100644 --- a/cli/tools/task.rs +++ b/cli/tools/task.rs @@ -36,6 +36,8 @@ use crate::args::TaskFlags; use crate::colors; use crate::factory::CliFactory; use crate::node::CliNodeResolver; +use crate::npm::installer::NpmInstaller; +use crate::npm::installer::PackageCaching; use crate::npm::CliNpmResolver; use crate::task_runner; use crate::task_runner::run_future_forwarding_signals; @@ -203,6 +205,7 @@ pub async fn execute_script( }] }; + let npm_installer = factory.npm_installer_if_managed()?; let npm_resolver = factory.npm_resolver().await?; let node_resolver = factory.node_resolver().await?; let env_vars = task_runner::real_env_vars(); @@ -216,6 +219,7 @@ pub async fn execute_script( let task_runner = TaskRunner { task_flags: &task_flags, + npm_installer: npm_installer.map(|n| n.as_ref()), npm_resolver: npm_resolver.as_ref(), node_resolver: node_resolver.as_ref(), env_vars, @@ -266,6 +270,7 @@ struct RunSingleOptions<'a> { struct TaskRunner<'a> { task_flags: &'a TaskFlags, + npm_installer: Option<&'a NpmInstaller>, npm_resolver: &'a dyn CliNpmResolver, node_resolver: &'a CliNodeResolver, env_vars: HashMap, @@ -458,11 +463,11 @@ impl<'a> TaskRunner<'a> { return Ok(0); }; - if let Some(npm_resolver) = self.npm_resolver.as_managed() { - npm_resolver.ensure_top_level_package_json_install().await?; - npm_resolver - .cache_packages(crate::npm::PackageCaching::All) + if let Some(npm_installer) = self.npm_installer { + npm_installer + .ensure_top_level_package_json_install() .await?; + npm_installer.cache_packages(PackageCaching::All).await?; } let cwd = match &self.task_flags.cwd { @@ -497,11 +502,11 @@ impl<'a> TaskRunner<'a> { argv: &[String], ) -> Result { // ensure the npm packages are installed if using a managed resolver - if let Some(npm_resolver) = self.npm_resolver.as_managed() { - npm_resolver.ensure_top_level_package_json_install().await?; - npm_resolver - .cache_packages(crate::npm::PackageCaching::All) + if let Some(npm_installer) = self.npm_installer { + npm_installer + .ensure_top_level_package_json_install() .await?; + npm_installer.cache_packages(PackageCaching::All).await?; } let cwd = match &self.task_flags.cwd { diff --git a/cli/tsc/mod.rs b/cli/tsc/mod.rs index f645a5f6b8..b6cf691c38 100644 --- a/cli/tsc/mod.rs +++ b/cli/tsc/mod.rs @@ -28,6 +28,7 @@ use deno_graph::GraphKind; use deno_graph::Module; use deno_graph::ModuleGraph; use deno_graph::ResolutionResolved; +use deno_resolver::npm::managed::ResolvePkgFolderFromDenoModuleError; use deno_resolver::npm::ResolvePkgFolderFromDenoReqError; use deno_semver::npm::NpmPackageReqReference; use node_resolver::errors::NodeJsErrorCode; @@ -709,9 +710,7 @@ pub enum ResolveError { PackageSubpathResolve(PackageSubpathResolveError), #[class(inherit)] #[error("{0}")] - ResolvePkgFolderFromDenoModule( - #[from] crate::npm::ResolvePkgFolderFromDenoModuleError, - ), + ResolvePkgFolderFromDenoModule(#[from] ResolvePkgFolderFromDenoModuleError), #[class(inherit)] #[error("{0}")] ResolveNonGraphSpecifierTypes(#[from] ResolveNonGraphSpecifierTypesError), diff --git a/cli/worker.rs b/cli/worker.rs index eee89d663c..d93c6c5f40 100644 --- a/cli/worker.rs +++ b/cli/worker.rs @@ -54,6 +54,8 @@ use crate::args::NpmCachingStrategy; use crate::args::StorageKeyResolver; use crate::node::CliNodeResolver; use crate::node::CliPackageJsonResolver; +use crate::npm::installer::NpmInstaller; +use crate::npm::installer::PackageCaching; use crate::npm::CliNpmResolver; use crate::sys::CliSys; use crate::util::checksum; @@ -148,6 +150,7 @@ struct SharedWorkerState { maybe_lockfile: Option>, module_loader_factory: Box, node_resolver: Arc, + npm_installer: Option>, npm_resolver: Arc, pkg_json_resolver: Arc, root_cert_store_provider: Arc, @@ -423,6 +426,7 @@ impl CliMainWorkerFactory { maybe_lockfile: Option>, module_loader_factory: Box, node_resolver: Arc, + npm_installer: Option>, npm_resolver: Arc, pkg_json_resolver: Arc, root_cert_store_provider: Arc, @@ -447,6 +451,7 @@ impl CliMainWorkerFactory { maybe_lockfile, module_loader_factory, node_resolver, + npm_installer, npm_resolver, pkg_json_resolver, root_cert_store_provider, @@ -496,18 +501,18 @@ impl CliMainWorkerFactory { let main_module = if let Ok(package_ref) = NpmPackageReqReference::from_specifier(&main_module) { - if let Some(npm_resolver) = shared.npm_resolver.as_managed() { + if let Some(npm_installer) = &shared.npm_installer { let reqs = &[package_ref.req().clone()]; - npm_resolver + npm_installer .add_package_reqs( reqs, if matches!( shared.default_npm_caching_strategy, NpmCachingStrategy::Lazy ) { - crate::npm::PackageCaching::Only(reqs.into()) + PackageCaching::Only(reqs.into()) } else { - crate::npm::PackageCaching::All + PackageCaching::All }, ) .await?; diff --git a/resolvers/deno/npm/managed/global.rs b/resolvers/deno/npm/managed/global.rs index cffe68a30e..d16234d8f4 100644 --- a/resolvers/deno/npm/managed/global.rs +++ b/resolvers/deno/npm/managed/global.rs @@ -16,7 +16,7 @@ use node_resolver::errors::ReferrerNotFoundError; use node_resolver::NpmPackageFolderResolver; use url::Url; -use super::resolution::NpmResolutionRc; +use super::resolution::NpmResolutionCellRc; use super::NpmCacheDirRc; use super::NpmPackageFsResolver; use crate::ResolvedNpmRcRc; @@ -26,14 +26,14 @@ use crate::ResolvedNpmRcRc; pub struct GlobalNpmPackageResolver { cache: NpmCacheDirRc, npm_rc: ResolvedNpmRcRc, - resolution: NpmResolutionRc, + resolution: NpmResolutionCellRc, } impl GlobalNpmPackageResolver { pub fn new( cache: NpmCacheDirRc, npm_rc: ResolvedNpmRcRc, - resolution: NpmResolutionRc, + resolution: NpmResolutionCellRc, ) -> Self { Self { cache, diff --git a/resolvers/deno/npm/managed/local.rs b/resolvers/deno/npm/managed/local.rs index 3aa1275d66..72b0b0d451 100644 --- a/resolvers/deno/npm/managed/local.rs +++ b/resolvers/deno/npm/managed/local.rs @@ -19,7 +19,7 @@ use sys_traits::FsCanonicalize; use sys_traits::FsMetadata; use url::Url; -use super::resolution::NpmResolutionRc; +use super::resolution::NpmResolutionCellRc; use super::NpmPackageFsResolver; use crate::npm::local::get_package_folder_id_folder_name_from_parts; use crate::npm::local::get_package_folder_id_from_folder_name; @@ -32,7 +32,7 @@ use crate::sync::MaybeSync; pub struct LocalNpmPackageResolver< TSys: FsCanonicalize + FsMetadata + MaybeSend + MaybeSync, > { - resolution: NpmResolutionRc, + resolution: NpmResolutionCellRc, sys: TSys, root_node_modules_path: PathBuf, root_node_modules_url: Url, @@ -43,7 +43,7 @@ impl { #[allow(clippy::too_many_arguments)] pub fn new( - resolution: NpmResolutionRc, + resolution: NpmResolutionCellRc, sys: TSys, node_modules_folder: PathBuf, ) -> Self { diff --git a/resolvers/deno/npm/managed/mod.rs b/resolvers/deno/npm/managed/mod.rs index a9775ee374..62147b2ac3 100644 --- a/resolvers/deno/npm/managed/mod.rs +++ b/resolvers/deno/npm/managed/mod.rs @@ -8,10 +8,13 @@ mod resolution; use std::path::Path; use std::path::PathBuf; +use deno_npm::resolution::PackageCacheFolderIdNotFoundError; +use deno_npm::resolution::PackageNvNotFoundError; use deno_npm::resolution::PackageReqNotFoundError; use deno_npm::NpmPackageCacheFolderId; use deno_npm::NpmPackageId; use deno_path_util::fs::canonicalize_path_maybe_not_exists; +use deno_semver::package::PackageNv; use deno_semver::package::PackageReq; use node_resolver::InNpmPackageChecker; use node_resolver::NpmPackageFolderResolver; @@ -23,14 +26,24 @@ use self::common::NpmPackageFsResolver; use self::common::NpmPackageFsResolverRc; use self::global::GlobalNpmPackageResolver; use self::local::LocalNpmPackageResolver; -pub use self::resolution::NpmResolution; -pub use self::resolution::NpmResolutionRc; +pub use self::resolution::NpmResolutionCell; +pub use self::resolution::NpmResolutionCellRc; use crate::sync::new_rc; use crate::sync::MaybeSend; use crate::sync::MaybeSync; use crate::NpmCacheDirRc; use crate::ResolvedNpmRcRc; +#[derive(Debug, thiserror::Error, deno_error::JsError)] +pub enum ResolvePkgFolderFromDenoModuleError { + #[class(inherit)] + #[error(transparent)] + PackageNvNotFound(#[from] PackageNvNotFoundError), + #[class(inherit)] + #[error(transparent)] + ResolvePkgFolderFromPkgId(#[from] ResolvePkgFolderFromPkgIdError), +} + #[derive(Debug, thiserror::Error, deno_error::JsError)] #[error(transparent)] pub enum ResolvePkgFolderFromPkgIdError { @@ -66,6 +79,16 @@ pub enum ManagedResolvePkgFolderFromDenoReqError { ResolvePkgFolderFromPkgId(#[from] ResolvePkgFolderFromPkgIdError), } +#[derive(Debug, thiserror::Error, deno_error::JsError)] +pub enum ResolvePkgIdFromSpecifierError { + #[class(inherit)] + #[error(transparent)] + Io(#[from] std::io::Error), + #[class(inherit)] + #[error(transparent)] + NotFound(#[from] PackageCacheFolderIdNotFoundError), +} + #[allow(clippy::disallowed_types)] pub type ManagedNpmResolverRc = crate::sync::MaybeArc>; @@ -73,7 +96,7 @@ pub type ManagedNpmResolverRc = #[derive(Debug)] pub struct ManagedNpmResolver { fs_resolver: NpmPackageFsResolverRc, - resolution: NpmResolutionRc, + resolution: NpmResolutionCellRc, sys: TSys, } @@ -89,7 +112,7 @@ impl ManagedNpmResolver { >( npm_cache_dir: &NpmCacheDirRc, npm_rc: &ResolvedNpmRcRc, - resolution: NpmResolutionRc, + resolution: NpmResolutionCellRc, sys: TCreateSys, maybe_node_modules_path: Option, ) -> ManagedNpmResolver { @@ -144,6 +167,14 @@ impl ManagedNpmResolver { Ok(path) } + pub fn resolve_pkg_folder_from_deno_module( + &self, + nv: &PackageNv, + ) -> Result { + let pkg_id = self.resolution.resolve_pkg_id_from_deno_module(nv)?; + Ok(self.resolve_pkg_folder_from_pkg_id(&pkg_id)?) + } + pub fn resolve_pkg_folder_from_deno_module_req( &self, req: &PackageReq, @@ -162,6 +193,24 @@ impl ManagedNpmResolver { .fs_resolver .resolve_package_cache_folder_id_from_specifier(specifier) } + + /// Resolves the package id from the provided specifier. + pub fn resolve_pkg_id_from_specifier( + &self, + specifier: &Url, + ) -> Result, ResolvePkgIdFromSpecifierError> { + let Some(cache_folder_id) = self + .fs_resolver + .resolve_package_cache_folder_id_from_specifier(specifier)? + else { + return Ok(None); + }; + Ok(Some( + self + .resolution + .resolve_pkg_id_from_pkg_cache_folder_id(&cache_folder_id)?, + )) + } } impl diff --git a/resolvers/deno/npm/managed/resolution.rs b/resolvers/deno/npm/managed/resolution.rs index 8ddd1ac70f..08dabf5a76 100644 --- a/resolvers/deno/npm/managed/resolution.rs +++ b/resolvers/deno/npm/managed/resolution.rs @@ -18,16 +18,17 @@ use deno_semver::package::PackageReq; use parking_lot::RwLock; #[allow(clippy::disallowed_types)] -pub type NpmResolutionRc = crate::sync::MaybeArc; +pub type NpmResolutionCellRc = crate::sync::MaybeArc; /// Handles updating and storing npm resolution in memory. /// /// This does not interact with the file system. -pub struct NpmResolution { +#[derive(Default)] +pub struct NpmResolutionCell { snapshot: RwLock, } -impl std::fmt::Debug for NpmResolution { +impl std::fmt::Debug for NpmResolutionCell { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let snapshot = self.snapshot.read(); f.debug_struct("NpmResolution") @@ -36,7 +37,7 @@ impl std::fmt::Debug for NpmResolution { } } -impl NpmResolution { +impl NpmResolutionCell { pub fn from_serialized( initial_snapshot: Option, ) -> Self { diff --git a/runtime/ops/process.rs b/runtime/ops/process.rs index 4c737f1126..4682085aea 100644 --- a/runtime/ops/process.rs +++ b/runtime/ops/process.rs @@ -140,7 +140,9 @@ pub trait NpmProcessStateProvider: #[derive(Debug)] pub struct EmptyNpmProcessStateProvider; + impl NpmProcessStateProvider for EmptyNpmProcessStateProvider {} + deno_core::extension!( deno_process, ops = [