diff --git a/Cargo.lock b/Cargo.lock index d55c1a19aa..e94fd48410 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2096,12 +2096,10 @@ dependencies = [ name = "deno_npm_cache" version = "0.3.0" dependencies = [ - "anyhow", "async-trait", "base64 0.21.7", "boxed_error", "deno_cache_dir", - "deno_core", "deno_error", "deno_npm", "deno_path_util", @@ -2197,16 +2195,20 @@ name = "deno_resolver" version = "0.15.0" dependencies = [ "anyhow", + "async-trait", "base32", "boxed_error", "dashmap", + "deno_cache_dir", "deno_config", "deno_error", "deno_media_type", + "deno_npm", "deno_package_json", "deno_path_util", "deno_semver", "node_resolver", + "parking_lot", "sys_traits", "test_server", "thiserror 2.0.3", diff --git a/cli/http_util.rs b/cli/http_util.rs index 19d9071833..5e63ab0a4a 100644 --- a/cli/http_util.rs +++ b/cli/http_util.rs @@ -70,7 +70,7 @@ impl HttpClientProvider { } } - pub fn get_or_create(&self) -> Result { + pub fn get_or_create(&self) -> Result { use std::collections::hash_map::Entry; let thread_id = std::thread::current().id(); let mut clients = self.clients_by_thread_id.lock(); @@ -87,7 +87,8 @@ impl HttpClientProvider { }, ..self.options.clone() }, - )?; + ) + .map_err(JsErrorBox::from_err)?; entry.insert(client.clone()); Ok(HttpClient::new(client)) } diff --git a/cli/npm/managed/installer.rs b/cli/npm/managed/installer.rs index 30ac807023..9f1cc05968 100644 --- a/cli/npm/managed/installer.rs +++ b/cli/npm/managed/installer.rs @@ -13,13 +13,13 @@ 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_semver::jsr::JsrDepPackageReq; use deno_semver::package::PackageNv; use deno_semver::package::PackageReq; use deno_semver::SmallStackString; use deno_semver::VersionReq; -use super::resolution::NpmResolution; use crate::args::CliLockfile; use crate::npm::CliNpmRegistryInfoProvider; use crate::util::sync::TaskQueue; diff --git a/cli/npm/managed/installers/global.rs b/cli/npm/managed/installers/global.rs index d637c96122..477d73dbfb 100644 --- a/cli/npm/managed/installers/global.rs +++ b/cli/npm/managed/installers/global.rs @@ -11,8 +11,8 @@ use deno_core::futures::StreamExt; use deno_error::JsErrorBox; use deno_npm::NpmResolutionPackage; use deno_npm::NpmSystemInfo; +use deno_resolver::npm::managed::NpmResolution; -use super::super::resolution::NpmResolution; use super::common::lifecycle_scripts::LifecycleScriptsStrategy; use super::common::NpmPackageFsInstaller; use crate::args::LifecycleScriptsConfig; diff --git a/cli/npm/managed/installers/local.rs b/cli/npm/managed/installers/local.rs index e2c5653801..a8efaaeb00 100644 --- a/cli/npm/managed/installers/local.rs +++ b/cli/npm/managed/installers/local.rs @@ -24,19 +24,19 @@ use deno_npm::resolution::NpmResolutionSnapshot; 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_semver::package::PackageNv; use deno_semver::StackString; use serde::Deserialize; use serde::Serialize; -use super::super::resolution::NpmResolution; use super::common::bin_entries; use super::common::NpmPackageFsInstaller; use crate::args::LifecycleScriptsConfig; use crate::args::NpmInstallDepsProvider; use crate::cache::CACHE_PERM; use crate::colors; -use crate::npm::managed::resolvers::get_package_folder_id_folder_name; use crate::npm::managed::PackageCaching; use crate::npm::CliNpmCache; use crate::npm::CliNpmTarballCache; diff --git a/cli/npm/managed/installers/mod.rs b/cli/npm/managed/installers/mod.rs index 4514fa1ec3..39e0d6f77c 100644 --- a/cli/npm/managed/installers/mod.rs +++ b/cli/npm/managed/installers/mod.rs @@ -4,11 +4,11 @@ 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 super::resolution::NpmResolution; use crate::args::LifecycleScriptsConfig; use crate::args::NpmInstallDepsProvider; use crate::npm::CliNpmCache; diff --git a/cli/npm/managed/mod.rs b/cli/npm/managed/mod.rs index 6729a56b1e..5950af948c 100644 --- a/cli/npm/managed/mod.rs +++ b/cli/npm/managed/mod.rs @@ -23,6 +23,10 @@ use deno_npm::NpmResolutionPackage; use deno_npm::NpmSystemInfo; use deno_npm_cache::NpmCacheSetting; use deno_path_util::fs::canonicalize_path_maybe_not_exists; +use deno_resolver::npm::managed::create_npm_fs_resolver; +use deno_resolver::npm::managed::NpmPackageFsResolver; +use deno_resolver::npm::managed::NpmPackageFsResolverPackageFolderError; +use deno_resolver::npm::managed::NpmResolution; use deno_resolver::npm::CliNpmReqResolver; use deno_runtime::colors; use deno_runtime::ops::process::NpmProcessStateProvider; @@ -37,9 +41,6 @@ use node_resolver::errors::PackageFolderResolveIoError; use node_resolver::InNpmPackageChecker; use node_resolver::NpmPackageFolderResolver; -use self::resolution::NpmResolution; -use self::resolvers::create_npm_fs_resolver; -use self::resolvers::NpmPackageFsResolver; use super::CliNpmCache; use super::CliNpmCacheHttpClient; use super::CliNpmRegistryInfoProvider; @@ -60,8 +61,6 @@ use crate::util::sync::AtomicFlag; mod installer; mod installers; -mod resolution; -mod resolvers; pub enum CliNpmResolverManagedSnapshotOption { ResolveFromLockfile(Arc), @@ -104,6 +103,7 @@ pub async fn create_managed_npm_resolver_for_lsp( create_inner( http_client, npm_cache, + options.npm_cache_dir, options.npm_install_deps_provider, npm_api, options.sys, @@ -133,6 +133,7 @@ pub async fn create_managed_npm_resolver( Ok(create_inner( http_client, npm_cache, + options.npm_cache_dir, options.npm_install_deps_provider, api, options.sys, @@ -150,6 +151,7 @@ pub async fn create_managed_npm_resolver( fn create_inner( http_client: Arc, npm_cache: Arc, + npm_cache_dir: Arc, npm_install_deps_provider: Arc, registry_info_provider: Arc, sys: CliSys, @@ -181,7 +183,8 @@ fn create_inner( lifecycle_scripts.clone(), ); let fs_resolver = create_npm_fs_resolver( - npm_cache.clone(), + &npm_cache_dir, + &npm_rc, resolution.clone(), sys.clone(), node_modules_dir_path, @@ -192,7 +195,9 @@ fn create_inner( maybe_lockfile, registry_info_provider, npm_cache, + npm_cache_dir, npm_install_deps_provider, + npm_rc, resolution, sys, tarball_cache, @@ -314,7 +319,9 @@ pub struct ManagedCliNpmResolver { maybe_lockfile: Option>, registry_info_provider: Arc, npm_cache: Arc, + npm_cache_dir: Arc, npm_install_deps_provider: Arc, + npm_rc: Arc, sys: CliSys, resolution: Arc, resolution_installer: NpmResolutionInstaller, @@ -338,7 +345,7 @@ pub enum ResolvePkgFolderFromPkgIdError { #[class(inherit)] #[error("{0}")] NpmPackageFsResolverPackageFolder( - #[from] resolvers::NpmPackageFsResolverPackageFolderError, + #[from] NpmPackageFsResolverPackageFolderError, ), #[class(inherit)] #[error("{0}")] @@ -363,7 +370,9 @@ impl ManagedCliNpmResolver { maybe_lockfile: Option>, registry_info_provider: Arc, npm_cache: Arc, + npm_cache_dir: Arc, npm_install_deps_provider: Arc, + npm_rc: Arc, resolution: Arc, sys: CliSys, tarball_cache: Arc, @@ -382,7 +391,9 @@ impl ManagedCliNpmResolver { maybe_lockfile, registry_info_provider, npm_cache, + npm_cache_dir, npm_install_deps_provider, + npm_rc, text_only_progress_bar, resolution, resolution_installer, @@ -671,11 +682,11 @@ impl ManagedCliNpmResolver { } pub fn global_cache_root_path(&self) -> &Path { - self.npm_cache.root_dir_path() + self.npm_cache_dir.root_dir() } pub fn global_cache_root_url(&self) -> &Url { - self.npm_cache.root_dir_url() + self.npm_cache_dir.root_dir_url() } } @@ -772,7 +783,8 @@ impl CliNpmResolver for ManagedCliNpmResolver { self.lifecycle_scripts.clone(), ), create_npm_fs_resolver( - self.npm_cache.clone(), + &self.npm_cache_dir, + &self.npm_rc, npm_resolution.clone(), self.sys.clone(), self.root_node_modules_path().map(ToOwned::to_owned), @@ -780,7 +792,9 @@ impl CliNpmResolver for ManagedCliNpmResolver { self.maybe_lockfile.clone(), self.registry_info_provider.clone(), self.npm_cache.clone(), + self.npm_cache_dir.clone(), self.npm_install_deps_provider.clone(), + self.npm_rc.clone(), npm_resolution, self.sys.clone(), self.tarball_cache.clone(), diff --git a/cli/npm/managed/resolvers/mod.rs b/cli/npm/managed/resolvers/mod.rs deleted file mode 100644 index 8a89208c77..0000000000 --- a/cli/npm/managed/resolvers/mod.rs +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright 2018-2025 the Deno authors. MIT license. - -mod common; -mod global; -mod local; - -use std::path::PathBuf; -use std::sync::Arc; - -pub use self::common::NpmPackageFsResolver; -pub use self::common::NpmPackageFsResolverPackageFolderError; -use self::global::GlobalNpmPackageResolver; -pub use self::local::get_package_folder_id_folder_name; -use self::local::LocalNpmPackageResolver; -use super::resolution::NpmResolution; -use crate::npm::CliNpmCache; -use crate::sys::CliSys; - -pub fn create_npm_fs_resolver( - npm_cache: Arc, - resolution: Arc, - sys: CliSys, - maybe_node_modules_path: Option, -) -> Arc { - match maybe_node_modules_path { - Some(node_modules_folder) => Arc::new(LocalNpmPackageResolver::new( - resolution, - sys, - node_modules_folder, - )), - None => Arc::new(GlobalNpmPackageResolver::new(npm_cache, resolution)), - } -} diff --git a/cli/npm/mod.rs b/cli/npm/mod.rs index d66b3e618f..56032e1a0b 100644 --- a/cli/npm/mod.rs +++ b/cli/npm/mod.rs @@ -11,6 +11,7 @@ use dashmap::DashMap; 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_resolver::npm::ByonmInNpmPackageChecker; @@ -100,7 +101,7 @@ impl deno_npm_cache::NpmCacheHttpClient for CliNpmCacheHttpClient { }; deno_npm_cache::DownloadError { status_code, - error: err.into(), + error: JsErrorBox::from_err(err), } }) } diff --git a/resolvers/deno/Cargo.toml b/resolvers/deno/Cargo.toml index 0eeec14e77..059fdbc08e 100644 --- a/resolvers/deno/Cargo.toml +++ b/resolvers/deno/Cargo.toml @@ -18,18 +18,20 @@ sync = ["dashmap"] [dependencies] anyhow.workspace = true +async-trait.workspace = true base32.workspace = true boxed_error.workspace = true dashmap = { workspace = true, optional = true } +deno_cache_dir.workspace = true deno_config.workspace = true deno_error.workspace = true deno_media_type.workspace = true -deno_package_json.workspace = true -deno_package_json.features = ["sync"] +deno_npm.workspace = true +deno_package_json = { workspace = true, features = ["sync"] } deno_path_util.workspace = true deno_semver.workspace = true -node_resolver.workspace = true -node_resolver.features = ["sync"] +node_resolver = { workspace = true, features = ["sync"] } +parking_lot.workspace = true sys_traits.workspace = true thiserror.workspace = true url.workspace = true diff --git a/resolvers/deno/lib.rs b/resolvers/deno/lib.rs index 49b2cf4d1b..12d8effc46 100644 --- a/resolvers/deno/lib.rs +++ b/resolvers/deno/lib.rs @@ -6,12 +6,14 @@ use std::path::PathBuf; use boxed_error::Boxed; +use deno_cache_dir::npm::NpmCacheDir; use deno_config::workspace::MappedResolution; use deno_config::workspace::MappedResolutionDiagnostic; use deno_config::workspace::MappedResolutionError; use deno_config::workspace::WorkspaceResolvePkgJsonFolderError; use deno_config::workspace::WorkspaceResolver; use deno_error::JsError; +use deno_npm::npm_rc::ResolvedNpmRc; use deno_package_json::PackageJsonDepValue; use deno_package_json::PackageJsonDepValueParseError; use deno_semver::npm::NpmPackageReqReference; @@ -47,6 +49,12 @@ mod sync; #[allow(clippy::disallowed_types)] pub type WorkspaceResolverRc = crate::sync::MaybeArc; +#[allow(clippy::disallowed_types)] +pub(crate) type ResolvedNpmRcRc = crate::sync::MaybeArc; + +#[allow(clippy::disallowed_types)] +pub(crate) type NpmCacheDirRc = crate::sync::MaybeArc; + #[derive(Debug, Clone)] pub struct DenoResolution { pub url: Url, diff --git a/resolvers/deno/npm/local.rs b/resolvers/deno/npm/local.rs index 6322a4b3f7..b05864b856 100644 --- a/resolvers/deno/npm/local.rs +++ b/resolvers/deno/npm/local.rs @@ -2,6 +2,58 @@ use std::borrow::Cow; +use deno_cache_dir::npm::mixed_case_package_name_decode; +use deno_npm::NpmPackageCacheFolderId; +use deno_semver::package::PackageNv; +use deno_semver::StackString; + +#[inline] +pub fn get_package_folder_id_folder_name( + folder_id: &NpmPackageCacheFolderId, +) -> String { + get_package_folder_id_folder_name_from_parts( + &folder_id.nv, + folder_id.copy_index, + ) +} + +pub fn get_package_folder_id_folder_name_from_parts( + nv: &PackageNv, + copy_index: u8, +) -> String { + let copy_str = if copy_index == 0 { + Cow::Borrowed("") + } else { + Cow::Owned(format!("_{}", copy_index)) + }; + let name = normalize_pkg_name_for_node_modules_deno_folder(&nv.name); + format!("{}@{}{}", name, nv.version, copy_str) +} + +pub fn get_package_folder_id_from_folder_name( + folder_name: &str, +) -> Option { + let folder_name = folder_name.replace('+', "/"); + let (name, ending) = folder_name.rsplit_once('@')?; + let name: StackString = if let Some(encoded_name) = name.strip_prefix('_') { + StackString::from_string(mixed_case_package_name_decode(encoded_name)?) + } else { + name.into() + }; + let (raw_version, copy_index) = match ending.split_once('_') { + Some((raw_version, copy_index)) => { + let copy_index = copy_index.parse::().ok()?; + (raw_version, copy_index) + } + None => (ending, 0), + }; + let version = deno_semver::Version::parse_from_npm(raw_version).ok()?; + Some(NpmPackageCacheFolderId { + nv: PackageNv { name, version }, + copy_index, + }) +} + /// Normalizes a package name for use at `node_modules/.deno/@[_]` pub fn normalize_pkg_name_for_node_modules_deno_folder(name: &str) -> Cow { let name = if name.to_lowercase() == name { @@ -25,3 +77,36 @@ fn mixed_case_package_name_encode(name: &str) -> String { ) .to_lowercase() } + +#[cfg(test)] +mod test { + use deno_npm::NpmPackageCacheFolderId; + use deno_semver::package::PackageNv; + + use super::*; + + #[test] + fn test_get_package_folder_id_folder_name() { + let cases = vec![ + ( + NpmPackageCacheFolderId { + nv: PackageNv::from_str("@types/foo@1.2.3").unwrap(), + copy_index: 1, + }, + "@types+foo@1.2.3_1".to_string(), + ), + ( + NpmPackageCacheFolderId { + nv: PackageNv::from_str("JSON@3.2.1").unwrap(), + copy_index: 0, + }, + "_jjju6tq@3.2.1".to_string(), + ), + ]; + for (input, output) in cases { + assert_eq!(get_package_folder_id_folder_name(&input), output); + let folder_id = get_package_folder_id_from_folder_name(&output).unwrap(); + assert_eq!(folder_id, input); + } + } +} diff --git a/cli/npm/managed/resolvers/common.rs b/resolvers/deno/npm/managed/common.rs similarity index 87% rename from cli/npm/managed/resolvers/common.rs rename to resolvers/deno/npm/managed/common.rs index be0b40fa2a..23ed63e2f0 100644 --- a/cli/npm/managed/resolvers/common.rs +++ b/resolvers/deno/npm/managed/common.rs @@ -4,10 +4,14 @@ use std::path::Path; use std::path::PathBuf; use async_trait::async_trait; -use deno_ast::ModuleSpecifier; use deno_npm::NpmPackageCacheFolderId; use deno_npm::NpmPackageId; use node_resolver::errors::PackageFolderResolveError; +use url::Url; + +#[allow(clippy::disallowed_types)] +pub(super) type NpmPackageFsResolverRc = + crate::sync::MaybeArc; #[derive(Debug, thiserror::Error, deno_error::JsError)] #[class(generic)] @@ -34,11 +38,11 @@ pub trait NpmPackageFsResolver: Send + Sync { fn resolve_package_folder_from_package( &self, name: &str, - referrer: &ModuleSpecifier, + referrer: &Url, ) -> Result; fn resolve_package_cache_folder_id_from_specifier( &self, - specifier: &ModuleSpecifier, + specifier: &Url, ) -> Result, std::io::Error>; } diff --git a/cli/npm/managed/resolvers/global.rs b/resolvers/deno/npm/managed/global.rs similarity index 62% rename from cli/npm/managed/resolvers/global.rs rename to resolvers/deno/npm/managed/global.rs index d63d3a7e45..16a2bc7a70 100644 --- a/cli/npm/managed/resolvers/global.rs +++ b/resolvers/deno/npm/managed/global.rs @@ -4,30 +4,60 @@ use std::path::Path; use std::path::PathBuf; -use std::sync::Arc; use async_trait::async_trait; -use deno_ast::ModuleSpecifier; use deno_npm::NpmPackageCacheFolderId; use deno_npm::NpmPackageId; +use deno_semver::package::PackageNv; +use deno_semver::StackString; +use deno_semver::Version; use node_resolver::errors::PackageFolderResolveError; use node_resolver::errors::PackageNotFoundError; use node_resolver::errors::ReferrerNotFoundError; +use url::Url; -use super::super::resolution::NpmResolution; -use super::common::NpmPackageFsResolver; -use crate::npm::CliNpmCache; +use super::resolution::NpmResolutionRc; +use super::NpmCacheDirRc; +use super::NpmPackageFsResolver; +use crate::ResolvedNpmRcRc; /// Resolves packages from the global npm cache. #[derive(Debug)] pub struct GlobalNpmPackageResolver { - cache: Arc, - resolution: Arc, + cache: NpmCacheDirRc, + npm_rc: ResolvedNpmRcRc, + resolution: NpmResolutionRc, } impl GlobalNpmPackageResolver { - pub fn new(cache: Arc, resolution: Arc) -> Self { - Self { cache, resolution } + pub fn new( + cache: NpmCacheDirRc, + npm_rc: ResolvedNpmRcRc, + resolution: NpmResolutionRc, + ) -> Self { + Self { + cache, + npm_rc, + resolution, + } + } + + fn resolve_package_cache_folder_id_from_specifier_inner( + &self, + specifier: &Url, + ) -> Option { + self + .cache + .resolve_package_folder_id_from_specifier(specifier) + .and_then(|cache_id| { + Some(NpmPackageCacheFolderId { + nv: PackageNv { + name: StackString::from_string(cache_id.name), + version: Version::parse_from_npm(&cache_id.version).ok()?, + }, + copy_index: cache_id.copy_index, + }) + }) } } @@ -38,21 +68,26 @@ impl NpmPackageFsResolver for GlobalNpmPackageResolver { } fn maybe_package_folder(&self, id: &NpmPackageId) -> Option { - let folder_id = self + let folder_copy_index = self .resolution - .resolve_pkg_cache_folder_id_from_pkg_id(id)?; - Some(self.cache.package_folder_for_id(&folder_id)) + .resolve_pkg_cache_folder_copy_index_from_pkg_id(id)?; + let registry_url = self.npm_rc.get_registry_url(&id.nv.name); + Some(self.cache.package_folder_for_id( + &id.nv.name, + &id.nv.version.to_string(), + folder_copy_index, + registry_url, + )) } fn resolve_package_folder_from_package( &self, name: &str, - referrer: &ModuleSpecifier, + referrer: &Url, ) -> Result { use deno_npm::resolution::PackageNotFoundFromReferrerError; - let Some(referrer_cache_folder_id) = self - .cache - .resolve_package_folder_id_from_specifier(referrer) + let Some(referrer_cache_folder_id) = + self.resolve_package_cache_folder_id_from_specifier_inner(referrer) else { return Err( ReferrerNotFoundError { @@ -106,12 +141,8 @@ impl NpmPackageFsResolver for GlobalNpmPackageResolver { fn resolve_package_cache_folder_id_from_specifier( &self, - specifier: &ModuleSpecifier, + specifier: &Url, ) -> Result, std::io::Error> { - Ok( - self - .cache - .resolve_package_folder_id_from_specifier(specifier), - ) + Ok(self.resolve_package_cache_folder_id_from_specifier_inner(specifier)) } } diff --git a/cli/npm/managed/resolvers/local.rs b/resolvers/deno/npm/managed/local.rs similarity index 61% rename from cli/npm/managed/resolvers/local.rs rename to resolvers/deno/npm/managed/local.rs index 52d5ac5a9e..e84de964ad 100644 --- a/cli/npm/managed/resolvers/local.rs +++ b/resolvers/deno/npm/managed/local.rs @@ -5,49 +5,50 @@ use std::borrow::Cow; use std::path::Path; use std::path::PathBuf; -use std::sync::Arc; use async_trait::async_trait; -use deno_ast::ModuleSpecifier; -use deno_cache_dir::npm::mixed_case_package_name_decode; -use deno_core::url::Url; use deno_npm::NpmPackageCacheFolderId; use deno_npm::NpmPackageId; use deno_path_util::fs::canonicalize_path_maybe_not_exists; -use deno_resolver::npm::normalize_pkg_name_for_node_modules_deno_folder; -use deno_semver::package::PackageNv; -use deno_semver::StackString; +use deno_path_util::url_from_directory_path; use node_resolver::errors::PackageFolderResolveError; use node_resolver::errors::PackageFolderResolveIoError; use node_resolver::errors::PackageNotFoundError; use node_resolver::errors::ReferrerNotFoundError; +use sys_traits::FsCanonicalize; use sys_traits::FsMetadata; +use url::Url; -use super::super::resolution::NpmResolution; -use super::common::NpmPackageFsResolver; -use crate::sys::CliSys; +use super::resolution::NpmResolutionRc; +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; /// Resolver that creates a local node_modules directory /// and resolves packages from it. #[derive(Debug)] -pub struct LocalNpmPackageResolver { - resolution: Arc, - sys: CliSys, +pub struct LocalNpmPackageResolver< + TSys: FsCanonicalize + FsMetadata + Send + Sync, +> { + resolution: NpmResolutionRc, + sys: TSys, root_node_modules_path: PathBuf, root_node_modules_url: Url, } -impl LocalNpmPackageResolver { +impl + LocalNpmPackageResolver +{ #[allow(clippy::too_many_arguments)] pub fn new( - resolution: Arc, - sys: CliSys, + resolution: NpmResolutionRc, + sys: TSys, node_modules_folder: PathBuf, ) -> Self { Self { resolution, sys, - root_node_modules_url: Url::from_directory_path(&node_modules_folder) + root_node_modules_url: url_from_directory_path(&node_modules_folder) .unwrap(), root_node_modules_path: node_modules_folder, } @@ -67,7 +68,7 @@ impl LocalNpmPackageResolver { fn resolve_folder_for_specifier( &self, - specifier: &ModuleSpecifier, + specifier: &Url, ) -> Result, std::io::Error> { let Some(relative_url) = self.root_node_modules_url.make_relative(specifier) @@ -78,7 +79,7 @@ impl LocalNpmPackageResolver { return Ok(None); } // it's within the directory, so use it - let Some(path) = specifier.to_file_path().ok() else { + let Some(path) = deno_path_util::url_to_file_path(specifier).ok() else { return Ok(None); }; // Canonicalize the path so it's not pointing to the symlinked directory @@ -88,7 +89,7 @@ impl LocalNpmPackageResolver { fn resolve_package_folder_from_specifier( &self, - specifier: &ModuleSpecifier, + specifier: &Url, ) -> Result, std::io::Error> { let Some(local_path) = self.resolve_folder_for_specifier(specifier)? else { return Ok(None); @@ -99,31 +100,36 @@ impl LocalNpmPackageResolver { } #[async_trait(?Send)] -impl NpmPackageFsResolver for LocalNpmPackageResolver { +impl NpmPackageFsResolver + for LocalNpmPackageResolver +{ fn node_modules_path(&self) -> Option<&Path> { Some(self.root_node_modules_path.as_ref()) } fn maybe_package_folder(&self, id: &NpmPackageId) -> Option { - let cache_folder_id = self + let folder_copy_index = self .resolution - .resolve_pkg_cache_folder_id_from_pkg_id(id)?; + .resolve_pkg_cache_folder_copy_index_from_pkg_id(id)?; // package is stored at: // node_modules/.deno//node_modules/ Some( self .root_node_modules_path .join(".deno") - .join(get_package_folder_id_folder_name(&cache_folder_id)) + .join(get_package_folder_id_folder_name_from_parts( + &id.nv, + folder_copy_index, + )) .join("node_modules") - .join(&cache_folder_id.nv.name), + .join(&id.nv.name), ) } fn resolve_package_folder_from_package( &self, name: &str, - referrer: &ModuleSpecifier, + referrer: &Url, ) -> Result { let maybe_local_path = self .resolve_folder_for_specifier(referrer) @@ -173,7 +179,7 @@ impl NpmPackageFsResolver for LocalNpmPackageResolver { fn resolve_package_cache_folder_id_from_specifier( &self, - specifier: &ModuleSpecifier, + specifier: &Url, ) -> Result, std::io::Error> { let Some(folder_path) = self.resolve_package_folder_from_specifier(specifier)? @@ -198,43 +204,6 @@ impl NpmPackageFsResolver for LocalNpmPackageResolver { } } -pub fn get_package_folder_id_folder_name( - folder_id: &NpmPackageCacheFolderId, -) -> String { - let copy_str = if folder_id.copy_index == 0 { - Cow::Borrowed("") - } else { - Cow::Owned(format!("_{}", folder_id.copy_index)) - }; - let nv = &folder_id.nv; - let name = normalize_pkg_name_for_node_modules_deno_folder(&nv.name); - format!("{}@{}{}", name, nv.version, copy_str) -} - -fn get_package_folder_id_from_folder_name( - folder_name: &str, -) -> Option { - let folder_name = folder_name.replace('+', "/"); - let (name, ending) = folder_name.rsplit_once('@')?; - let name: StackString = if let Some(encoded_name) = name.strip_prefix('_') { - StackString::from_string(mixed_case_package_name_decode(encoded_name)?) - } else { - name.into() - }; - let (raw_version, copy_index) = match ending.split_once('_') { - Some((raw_version, copy_index)) => { - let copy_index = copy_index.parse::().ok()?; - (raw_version, copy_index) - } - None => (ending, 0), - }; - let version = deno_semver::Version::parse_from_npm(raw_version).ok()?; - Some(NpmPackageCacheFolderId { - nv: PackageNv { name, version }, - copy_index, - }) -} - fn join_package_name(path: &Path, package_name: &str) -> PathBuf { let mut path = path.to_path_buf(); // ensure backslashes are used on windows @@ -243,36 +212,3 @@ fn join_package_name(path: &Path, package_name: &str) -> PathBuf { } path } - -#[cfg(test)] -mod test { - use deno_npm::NpmPackageCacheFolderId; - use deno_semver::package::PackageNv; - - use super::*; - - #[test] - fn test_get_package_folder_id_folder_name() { - let cases = vec![ - ( - NpmPackageCacheFolderId { - nv: PackageNv::from_str("@types/foo@1.2.3").unwrap(), - copy_index: 1, - }, - "@types+foo@1.2.3_1".to_string(), - ), - ( - NpmPackageCacheFolderId { - nv: PackageNv::from_str("JSON@3.2.1").unwrap(), - copy_index: 0, - }, - "_jjju6tq@3.2.1".to_string(), - ), - ]; - for (input, output) in cases { - assert_eq!(get_package_folder_id_folder_name(&input), output); - let folder_id = get_package_folder_id_from_folder_name(&output).unwrap(); - assert_eq!(folder_id, input); - } - } -} diff --git a/resolvers/deno/npm/managed/mod.rs b/resolvers/deno/npm/managed/mod.rs new file mode 100644 index 0000000000..b460656eab --- /dev/null +++ b/resolvers/deno/npm/managed/mod.rs @@ -0,0 +1,45 @@ +// Copyright 2018-2025 the Deno authors. MIT license. + +mod common; +mod global; +mod local; +mod resolution; + +use std::path::PathBuf; + +use sys_traits::FsCanonicalize; +use sys_traits::FsMetadata; + +pub use self::common::NpmPackageFsResolver; +pub use self::common::NpmPackageFsResolverPackageFolderError; +use self::common::NpmPackageFsResolverRc; +use self::global::GlobalNpmPackageResolver; +use self::local::LocalNpmPackageResolver; +pub use self::resolution::NpmResolution; +use self::resolution::NpmResolutionRc; +use crate::sync::new_rc; +use crate::NpmCacheDirRc; +use crate::ResolvedNpmRcRc; + +pub fn create_npm_fs_resolver< + TSys: FsCanonicalize + FsMetadata + Send + Sync + 'static, +>( + npm_cache_dir: &NpmCacheDirRc, + npm_rc: &ResolvedNpmRcRc, + resolution: NpmResolutionRc, + sys: TSys, + maybe_node_modules_path: Option, +) -> NpmPackageFsResolverRc { + match maybe_node_modules_path { + Some(node_modules_folder) => new_rc(LocalNpmPackageResolver::new( + resolution, + sys, + node_modules_folder, + )), + None => new_rc(GlobalNpmPackageResolver::new( + npm_cache_dir.clone(), + npm_rc.clone(), + resolution, + )), + } +} diff --git a/cli/npm/managed/resolution.rs b/resolvers/deno/npm/managed/resolution.rs similarity index 91% rename from cli/npm/managed/resolution.rs rename to resolvers/deno/npm/managed/resolution.rs index 5178c20608..40ab91202c 100644 --- a/cli/npm/managed/resolution.rs +++ b/resolvers/deno/npm/managed/resolution.rs @@ -2,7 +2,6 @@ use std::collections::HashMap; -use deno_core::parking_lot::RwLock; use deno_npm::resolution::NpmPackagesPartitioned; use deno_npm::resolution::NpmResolutionSnapshot; use deno_npm::resolution::PackageCacheFolderIdNotFoundError; @@ -16,10 +15,12 @@ use deno_npm::NpmResolutionPackage; use deno_npm::NpmSystemInfo; use deno_semver::package::PackageNv; use deno_semver::package::PackageReq; +use parking_lot::RwLock; -/// Handles updating and storing npm resolution in memory where the underlying -/// snapshot can be updated concurrently. Additionally handles updating the lockfile -/// based on changes to the resolution. +#[allow(clippy::disallowed_types)] +pub(super) type NpmResolutionRc = crate::sync::MaybeArc; + +/// Handles updating and storing npm resolution in memory. /// /// This does not interact with the file system. pub struct NpmResolution { @@ -50,15 +51,15 @@ impl NpmResolution { } } - pub fn resolve_pkg_cache_folder_id_from_pkg_id( + pub fn resolve_pkg_cache_folder_copy_index_from_pkg_id( &self, id: &NpmPackageId, - ) -> Option { + ) -> Option { self .snapshot .read() .package_from_id(id) - .map(|p| p.get_package_cache_folder_id()) + .map(|p| p.copy_index) } pub fn resolve_pkg_id_from_pkg_cache_folder_id( diff --git a/resolvers/deno/npm/mod.rs b/resolvers/deno/npm/mod.rs index 2e7c4c888b..54282e677d 100644 --- a/resolvers/deno/npm/mod.rs +++ b/resolvers/deno/npm/mod.rs @@ -4,15 +4,9 @@ use std::fmt::Debug; use std::path::PathBuf; use boxed_error::Boxed; -pub use byonm::ByonmInNpmPackageChecker; -pub use byonm::ByonmNpmResolver; -pub use byonm::ByonmNpmResolverCreateOptions; -pub use byonm::ByonmNpmResolverRc; -pub use byonm::ByonmResolvePkgFolderFromDenoReqError; use deno_error::JsError; use deno_semver::npm::NpmPackageReqReference; use deno_semver::package::PackageReq; -pub use local::normalize_pkg_name_for_node_modules_deno_folder; use node_resolver::errors::NodeResolveError; use node_resolver::errors::NodeResolveErrorKind; use node_resolver::errors::PackageFolderResolveErrorKind; @@ -33,8 +27,17 @@ use sys_traits::FsReadDir; use thiserror::Error; use url::Url; +pub use self::byonm::ByonmInNpmPackageChecker; +pub use self::byonm::ByonmNpmResolver; +pub use self::byonm::ByonmNpmResolverCreateOptions; +pub use self::byonm::ByonmNpmResolverRc; +pub use self::byonm::ByonmResolvePkgFolderFromDenoReqError; +pub use self::local::get_package_folder_id_folder_name; +pub use self::local::normalize_pkg_name_for_node_modules_deno_folder; + mod byonm; mod local; +pub mod managed; #[derive(Debug, Error, JsError)] #[class(generic)] diff --git a/resolvers/deno/sync.rs b/resolvers/deno/sync.rs index 43635e62b7..10cdc5ec4d 100644 --- a/resolvers/deno/sync.rs +++ b/resolvers/deno/sync.rs @@ -46,3 +46,9 @@ mod inner { } } } + +#[allow(clippy::disallowed_types)] +#[inline] +pub fn new_rc(value: T) -> MaybeArc { + MaybeArc::new(value) +} diff --git a/resolvers/npm_cache/Cargo.toml b/resolvers/npm_cache/Cargo.toml index 328355d340..3cefc6538a 100644 --- a/resolvers/npm_cache/Cargo.toml +++ b/resolvers/npm_cache/Cargo.toml @@ -14,11 +14,6 @@ description = "Helpers for downloading and caching npm dependencies for Deno" path = "lib.rs" [dependencies] -# todo(dsherret): remove this dependency -anyhow.workspace = true -# todo(dsherret): remove this dependency -deno_core.workspace = true - async-trait.workspace = true base64.workspace = true boxed_error.workspace = true diff --git a/resolvers/npm_cache/lib.rs b/resolvers/npm_cache/lib.rs index 90c07de5d5..f0de201b75 100644 --- a/resolvers/npm_cache/lib.rs +++ b/resolvers/npm_cache/lib.rs @@ -6,7 +6,6 @@ use std::path::Path; use std::path::PathBuf; use std::sync::Arc; -use anyhow::Error as AnyError; use deno_cache_dir::file_fetcher::CacheSetting; use deno_cache_dir::npm::NpmCacheDir; use deno_error::JsErrorBox; @@ -51,7 +50,7 @@ pub use tarball::TarballCache; #[class(generic)] pub struct DownloadError { pub status_code: Option, - pub error: AnyError, + pub error: JsErrorBox, } impl std::error::Error for DownloadError { @@ -312,15 +311,17 @@ impl< &self, name: &str, package_info: &NpmPackageInfo, - ) -> Result<(), AnyError> { + ) -> Result<(), JsErrorBox> { let file_cache_path = self.get_registry_package_info_file_cache_path(name); - let file_text = serde_json::to_string(&package_info)?; + let file_text = + serde_json::to_string(&package_info).map_err(JsErrorBox::from_err)?; atomic_write_file_with_retries( &self.sys, &file_cache_path, file_text.as_bytes(), 0o644, - )?; + ) + .map_err(JsErrorBox::from_err)?; Ok(()) } diff --git a/resolvers/npm_cache/registry_info.rs b/resolvers/npm_cache/registry_info.rs index ece797abba..673f2ff445 100644 --- a/resolvers/npm_cache/registry_info.rs +++ b/resolvers/npm_cache/registry_info.rs @@ -5,11 +5,6 @@ use std::collections::HashSet; use std::sync::Arc; use async_trait::async_trait; -use deno_core::futures::future::LocalBoxFuture; -use deno_core::futures::FutureExt; -use deno_core::parking_lot::Mutex; -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; @@ -17,6 +12,9 @@ use deno_npm::registry::NpmRegistryApi; use deno_npm::registry::NpmRegistryPackageInfoLoadError; use deno_unsync::sync::AtomicFlag; use deno_unsync::sync::MultiRuntimeAsyncValueCreator; +use futures::future::LocalBoxFuture; +use futures::FutureExt; +use parking_lot::Mutex; use sys_traits::FsCreateDirAll; use sys_traits::FsHardLink; use sys_traits::FsMetadata; @@ -26,6 +24,7 @@ use sys_traits::FsRemoveFile; use sys_traits::FsRename; use sys_traits::SystemRandom; use sys_traits::ThreadSleep; +use url::Url; use crate::remote::maybe_auth_header_for_npm_registry; use crate::NpmCache; diff --git a/resolvers/npm_cache/tarball.rs b/resolvers/npm_cache/tarball.rs index d9575ba9cd..7a9453e655 100644 --- a/resolvers/npm_cache/tarball.rs +++ b/resolvers/npm_cache/tarball.rs @@ -3,16 +3,15 @@ use std::collections::HashMap; use std::sync::Arc; -use deno_core::futures::future::LocalBoxFuture; -use deno_core::futures::FutureExt; -use deno_core::parking_lot::Mutex; -use deno_core::url::Url; use deno_error::JsErrorBox; use deno_npm::npm_rc::ResolvedNpmRc; use deno_npm::registry::NpmPackageVersionDistInfo; use deno_semver::package::PackageNv; use deno_unsync::sync::MultiRuntimeAsyncValueCreator; +use futures::future::LocalBoxFuture; +use futures::FutureExt; use http::StatusCode; +use parking_lot::Mutex; use sys_traits::FsCreateDirAll; use sys_traits::FsHardLink; use sys_traits::FsMetadata; @@ -22,6 +21,7 @@ use sys_traits::FsRemoveFile; use sys_traits::FsRename; use sys_traits::SystemRandom; use sys_traits::ThreadSleep; +use url::Url; use crate::remote::maybe_auth_header_for_npm_registry; use crate::tarball_extract::verify_and_extract_tarball; diff --git a/resolvers/npm_cache/todo.md b/resolvers/npm_cache/todo.md index e10b1cfd89..e460fcae86 100644 --- a/resolvers/npm_cache/todo.md +++ b/resolvers/npm_cache/todo.md @@ -1,7 +1,5 @@ This crate is a work in progress: -1. Remove `deno_core` dependency. -1. Remove `anyhow` dependency. 1. Add a clippy.toml file that bans accessing the file system directory and instead does it through a trait. 1. Make this crate work in Wasm.