// Copyright 2018-2025 the Deno authors. MIT license. use std::sync::Arc; use deno_npm::resolution::PackageIdNotFoundError; use deno_npm::resolution::ValidSerializedNpmResolutionSnapshot; use deno_npm_cache::NpmCache; use deno_npm_cache::NpmCacheHttpClient; use deno_npm_cache::NpmCacheSetting; use deno_npm_cache::RegistryInfoProvider; use deno_npm_cache::TarballCache; use deno_resolver::factory::ResolverFactory; use deno_resolver::factory::WorkspaceFactory; use deno_resolver::factory::WorkspaceFactorySys; use deno_resolver::lockfile::LockfileLock; use deno_resolver::lockfile::LockfileNpmPackageInfoApiAdapter; use futures::FutureExt; use crate::LifecycleScriptsConfig; use crate::NpmInstaller; use crate::Reporter; use crate::graph::NpmCachingStrategy; use crate::graph::NpmDenoGraphResolver; use crate::initializer::NpmResolutionInitializer; use crate::initializer::NpmResolverManagedSnapshotOption; use crate::lifecycle_scripts::LifecycleScriptsExecutor; use crate::package_json::NpmInstallDepsProvider; use crate::resolution::NpmResolutionInstaller; // todo(https://github.com/rust-lang/rust/issues/109737): remove once_cell after get_or_try_init is stabilized type Deferred = once_cell::sync::OnceCell; #[sys_traits::auto_impl] pub trait NpmInstallerFactorySys: crate::NpmInstallerSys + WorkspaceFactorySys { } type ResolveNpmResolutionSnapshotFn = Box< dyn Fn() -> Result< Option, PackageIdNotFoundError, >, >; pub struct NpmInstallerFactoryOptions { pub cache_setting: NpmCacheSetting, pub caching_strategy: NpmCachingStrategy, pub lifecycle_scripts_config: LifecycleScriptsConfig, /// Resolves the npm resolution snapshot from the environment. pub resolve_npm_resolution_snapshot: ResolveNpmResolutionSnapshotFn, } pub trait InstallReporter: deno_npm::resolution::Reporter + deno_graph::source::Reporter + deno_npm_cache::TarballCacheReporter + crate::InstallProgressReporter { } impl< T: deno_npm::resolution::Reporter + deno_graph::source::Reporter + deno_npm_cache::TarballCacheReporter + crate::InstallProgressReporter, > InstallReporter for T { } pub struct NpmInstallerFactory< TNpmCacheHttpClient: NpmCacheHttpClient, TReporter: Reporter, TSys: NpmInstallerFactorySys, > { resolver_factory: Arc>, http_client: Arc, lifecycle_scripts_executor: Arc, reporter: TReporter, lockfile_npm_package_info_provider: Deferred, npm_cache: Deferred>>, npm_deno_graph_resolver: async_once_cell::OnceCell< Arc>, >, npm_installer: async_once_cell::OnceCell>>, npm_resolution_initializer: async_once_cell::OnceCell>>, npm_resolution_installer: async_once_cell::OnceCell< Arc>, >, registry_info_provider: Deferred>>, tarball_cache: Deferred>>, options: NpmInstallerFactoryOptions, install_reporter: Option>, } impl< TNpmCacheHttpClient: NpmCacheHttpClient, TReporter: Reporter, TSys: NpmInstallerFactorySys, > NpmInstallerFactory { pub fn new( resolver_factory: Arc>, http_client: Arc, lifecycle_scripts_executor: Arc, reporter: TReporter, install_reporter: Option>, options: NpmInstallerFactoryOptions, ) -> Self { Self { resolver_factory, http_client, lifecycle_scripts_executor, reporter, lockfile_npm_package_info_provider: Default::default(), npm_cache: Default::default(), npm_deno_graph_resolver: Default::default(), npm_installer: Default::default(), npm_resolution_initializer: Default::default(), npm_resolution_installer: Default::default(), registry_info_provider: Default::default(), tarball_cache: Default::default(), install_reporter, options, } } pub fn http_client(&self) -> &Arc { &self.http_client } pub async fn initialize_npm_resolution_if_managed( &self, ) -> Result<(), anyhow::Error> { let npm_resolver = self.resolver_factory().npm_resolver()?; if npm_resolver.is_managed() { self .npm_resolution_initializer() .await? .ensure_initialized() .await?; } Ok(()) } pub fn lockfile_npm_package_info_provider( &self, ) -> Result<&LockfileNpmPackageInfoApiAdapter, anyhow::Error> { self.lockfile_npm_package_info_provider.get_or_try_init(|| { Ok(LockfileNpmPackageInfoApiAdapter::new( self.registry_info_provider()?.clone(), self .workspace_factory() .workspace_npm_link_packages()? .clone(), )) }) } pub async fn maybe_lockfile( &self, ) -> Result>>, anyhow::Error> { let workspace_factory = self.workspace_factory(); let package_info_provider = self.lockfile_npm_package_info_provider()?; workspace_factory .maybe_lockfile(package_info_provider) .await } pub fn npm_cache(&self) -> Result<&Arc>, anyhow::Error> { self.npm_cache.get_or_try_init(|| { let workspace_factory = self.workspace_factory(); Ok(Arc::new(NpmCache::new( workspace_factory.npm_cache_dir()?.clone(), workspace_factory.sys().clone(), self.options.cache_setting.clone(), workspace_factory.npmrc()?.clone(), ))) }) } pub async fn npm_deno_graph_resolver( &self, ) -> Result< &Arc>, anyhow::Error, > { self .npm_deno_graph_resolver .get_or_try_init( async { Ok(Arc::new(NpmDenoGraphResolver::new( self.npm_installer_if_managed().await?.cloned(), self .resolver_factory() .found_package_json_dep_flag() .clone(), self.options.caching_strategy, ))) } .boxed_local(), ) .await } pub async fn npm_resolution_initializer( &self, ) -> Result<&Arc>, anyhow::Error> { self .npm_resolution_initializer .get_or_try_init(async move { let workspace_factory = self.workspace_factory(); Ok(Arc::new(NpmResolutionInitializer::new( self.resolver_factory.npm_resolution().clone(), workspace_factory.workspace_npm_link_packages()?.clone(), match (self.options.resolve_npm_resolution_snapshot)()? { Some(snapshot) => { NpmResolverManagedSnapshotOption::Specified(Some(snapshot)) } None => match self.maybe_lockfile().await? { Some(lockfile) => { NpmResolverManagedSnapshotOption::ResolveFromLockfile( lockfile.clone(), ) } None => NpmResolverManagedSnapshotOption::Specified(None), }, }, ))) }) .await } pub async fn npm_resolution_installer( &self, ) -> Result< &Arc>, anyhow::Error, > { self .npm_resolution_installer .get_or_try_init(async move { Ok(Arc::new(NpmResolutionInstaller::new( self.registry_info_provider()?.clone(), self.resolver_factory.npm_resolution().clone(), self.maybe_lockfile().await?.cloned(), self .workspace_factory() .workspace_npm_link_packages()? .clone(), self .install_reporter .as_ref() .map(|r| r.clone() as Arc), ))) }) .await } pub async fn npm_installer_if_managed( &self, ) -> Result< Option<&Arc>>, anyhow::Error, > { if self.resolver_factory().use_byonm()? || self.workspace_factory().no_npm() { Ok(None) } else { Ok(Some(self.npm_installer().await?)) } } pub async fn npm_installer( &self, ) -> Result<&Arc>, anyhow::Error> { self .npm_installer .get_or_try_init( async move { let workspace_factory = self.workspace_factory(); let npm_cache = self.npm_cache()?; let registry_info_provider = self.registry_info_provider()?; let workspace_npm_link_packages = workspace_factory.workspace_npm_link_packages()?; Ok(Arc::new(NpmInstaller::new( self.lifecycle_scripts_executor.clone(), npm_cache.clone(), Arc::new(NpmInstallDepsProvider::from_workspace( &workspace_factory.workspace_directory()?.workspace, )), registry_info_provider.clone(), self.resolver_factory.npm_resolution().clone(), self.npm_resolution_initializer().await?.clone(), self.npm_resolution_installer().await?.clone(), &self.reporter, workspace_factory.sys().clone(), self.tarball_cache()?.clone(), self.maybe_lockfile().await?.cloned(), workspace_factory .node_modules_dir_path()? .map(|p| p.to_path_buf()), self.options.lifecycle_scripts_config.clone(), self.resolver_factory.npm_system_info().clone(), workspace_npm_link_packages.clone(), self.install_reporter.clone(), ))) } .boxed_local(), ) .await } pub fn registry_info_provider( &self, ) -> Result< &Arc>, anyhow::Error, > { self.registry_info_provider.get_or_try_init(|| { Ok(Arc::new(RegistryInfoProvider::new( self.npm_cache()?.clone(), self.http_client().clone(), self.workspace_factory().npmrc()?.clone(), ))) }) } pub fn tarball_cache( &self, ) -> Result<&Arc>, anyhow::Error> { self.tarball_cache.get_or_try_init(|| { let workspace_factory = self.workspace_factory(); Ok(Arc::new(TarballCache::new( self.npm_cache()?.clone(), self.http_client.clone(), workspace_factory.sys().clone(), workspace_factory.npmrc()?.clone(), self .install_reporter .as_ref() .map(|r| r.clone() as Arc), ))) }) } pub fn resolver_factory(&self) -> &Arc> { &self.resolver_factory } pub fn workspace_factory(&self) -> &Arc> { self.resolver_factory.workspace_factory() } }