diff --git a/cli/args/mod.rs b/cli/args/mod.rs index b3d508e18a236a..cee1d8fef4529b 100644 --- a/cli/args/mod.rs +++ b/cli/args/mod.rs @@ -13,6 +13,8 @@ use ::import_map::ImportMap; use deno_ast::SourceMapOption; use deno_core::resolve_url_or_path; use deno_graph::GraphKind; +use deno_npm::npm_rc::NpmRc; +use deno_npm::npm_rc::ResolvedNpmRc; use deno_npm::resolution::ValidSerializedNpmResolutionSnapshot; use deno_npm::NpmSystemInfo; use deno_runtime::deno_tls::RootCertStoreProvider; @@ -546,6 +548,83 @@ fn discover_package_json( Ok(None) } +/// Discover `.npmrc` file - currently we only support it next to `package.json` +/// or next to `deno.json`. +/// +/// In the future we will need to support it in user directory or global directory +/// as per https://docs.npmjs.com/cli/v10/configuring-npm/npmrc#files. +fn discover_npmrc( + maybe_package_json_path: Option, + maybe_deno_json_path: Option, +) -> Result, AnyError> { + if !*DENO_FUTURE { + return Ok(create_default_npmrc()); + } + + const NPMRC_NAME: &str = ".npmrc"; + + fn get_env_var(var_name: &str) -> Option { + std::env::var(var_name).ok() + } + + fn try_to_read_npmrc( + dir: &Path, + ) -> Result, AnyError> { + let path = dir.join(NPMRC_NAME); + let maybe_source = match std::fs::read_to_string(&path) { + Ok(source) => Some(source), + Err(err) if err.kind() == std::io::ErrorKind::NotFound => None, + Err(err) => { + bail!("Error loading .npmrc at {}. {:#}", path.display(), err) + } + }; + + Ok(maybe_source.map(|source| (source, path))) + } + + fn try_to_parse_npmrc( + source: String, + path: &Path, + ) -> Result, AnyError> { + let npmrc = NpmRc::parse(&source, &get_env_var).with_context(|| { + format!("Failed to parse .npmrc at {}", path.display()) + })?; + let resolved = npmrc + .as_resolved(npm_registry_url()) + .context("Failed to resolve .npmrc options")?; + Ok(Arc::new(resolved)) + } + + if let Some(package_json_path) = maybe_package_json_path { + if let Some(package_json_dir) = package_json_path.parent() { + if let Some((source, path)) = try_to_read_npmrc(package_json_dir)? { + return try_to_parse_npmrc(source, &path); + } + } + } + + if let Some(deno_json_path) = maybe_deno_json_path { + if let Some(deno_json_dir) = deno_json_path.parent() { + if let Some((source, path)) = try_to_read_npmrc(deno_json_dir)? { + return try_to_parse_npmrc(source, &path); + } + } + } + + log::debug!("No .npmrc file found"); + Ok(create_default_npmrc()) +} + +pub fn create_default_npmrc() -> Arc { + Arc::new(ResolvedNpmRc { + default_config: deno_npm::npm_rc::RegistryConfigWithUrl { + registry_url: npm_registry_url().clone(), + config: Default::default(), + }, + scopes: Default::default(), + }) +} + struct CliRootCertStoreProvider { cell: OnceCell, maybe_root_path: Option, @@ -722,6 +801,7 @@ pub struct CliOptions { maybe_vendor_folder: Option, maybe_config_file: Option, maybe_package_json: Option, + npmrc: Arc, maybe_lockfile: Option>>, overrides: CliOptionOverrides, maybe_workspace_config: Option, @@ -736,6 +816,7 @@ impl CliOptions { maybe_config_file: Option, maybe_lockfile: Option>>, maybe_package_json: Option, + npmrc: Arc, force_global_cache: bool, ) -> Result { if let Some(insecure_allowlist) = @@ -798,6 +879,7 @@ impl CliOptions { maybe_config_file, maybe_lockfile, maybe_package_json, + npmrc, maybe_node_modules_folder, maybe_vendor_folder, overrides: Default::default(), @@ -851,6 +933,16 @@ impl CliOptions { } else { maybe_package_json = discover_package_json(&flags, None, &initial_cwd)?; } + let npmrc = discover_npmrc( + maybe_package_json.as_ref().map(|p| p.path.clone()), + maybe_config_file.as_ref().and_then(|cf| { + if cf.specifier.scheme() == "file" { + Some(cf.specifier.to_file_path().unwrap()) + } else { + None + } + }), + )?; let maybe_lock_file = lockfile::discover(&flags, maybe_config_file.as_ref())?; @@ -860,6 +952,7 @@ impl CliOptions { maybe_config_file, maybe_lock_file.map(|l| Arc::new(Mutex::new(l))), maybe_package_json, + npmrc, false, ) } @@ -1169,6 +1262,7 @@ impl CliOptions { maybe_vendor_folder: self.maybe_vendor_folder.clone(), maybe_config_file: self.maybe_config_file.clone(), maybe_package_json: self.maybe_package_json.clone(), + npmrc: self.npmrc.clone(), maybe_lockfile: self.maybe_lockfile.clone(), maybe_workspace_config: self.maybe_workspace_config.clone(), overrides: self.overrides.clone(), @@ -1300,6 +1394,10 @@ impl CliOptions { &self.maybe_package_json } + pub fn npmrc(&self) -> &Arc { + &self.npmrc + } + pub fn maybe_package_json_deps(&self) -> Option { if matches!( self.flags.subcommand, diff --git a/cli/factory.rs b/cli/factory.rs index 8a9d20970a1c09..43486bca085f0e 100644 --- a/cli/factory.rs +++ b/cli/factory.rs @@ -446,7 +446,7 @@ impl CliFactory { self.package_json_deps_provider().clone(), ), npm_system_info: self.options.npm_system_info(), - npm_registry_url: crate::args::npm_registry_url().to_owned(), + npmrc: self.options.npmrc().clone() }) }).await }.boxed_local()) diff --git a/cli/http_util.rs b/cli/http_util.rs index 30507a629b314c..832ccec1c0b75a 100644 --- a/cli/http_util.rs +++ b/cli/http_util.rs @@ -17,6 +17,8 @@ use deno_runtime::deno_fetch::reqwest::header::LOCATION; use deno_runtime::deno_fetch::reqwest::Response; use deno_runtime::deno_fetch::CreateHttpClientOptions; use deno_runtime::deno_tls::RootCertStoreProvider; +use reqwest::header::HeaderName; +use reqwest::header::HeaderValue; use std::collections::HashMap; use std::sync::Arc; use std::time::Duration; @@ -294,7 +296,7 @@ impl HttpClient { &self, url: U, ) -> Result, AnyError> { - let maybe_bytes = self.inner_download(url, None).await?; + let maybe_bytes = self.inner_download(url, None, None).await?; match maybe_bytes { Some(bytes) => Ok(bytes), None => Err(custom_error("Http", "Not found.")), @@ -304,17 +306,21 @@ impl HttpClient { pub async fn download_with_progress( &self, url: U, + maybe_header: Option<(HeaderName, HeaderValue)>, progress_guard: &UpdateGuard, ) -> Result>, AnyError> { - self.inner_download(url, Some(progress_guard)).await + self + .inner_download(url, maybe_header, Some(progress_guard)) + .await } async fn inner_download( &self, url: U, + maybe_header: Option<(HeaderName, HeaderValue)>, progress_guard: Option<&UpdateGuard>, ) -> Result>, AnyError> { - let response = self.get_redirected_response(url).await?; + let response = self.get_redirected_response(url, maybe_header).await?; if response.status() == 404 { return Ok(None); @@ -339,15 +345,30 @@ impl HttpClient { pub async fn get_redirected_response( &self, url: U, + mut maybe_header: Option<(HeaderName, HeaderValue)>, ) -> Result { let mut url = url.into_url()?; - let mut response = self.get_no_redirect(url.clone())?.send().await?; + + let mut builder = self.get_no_redirect(url.clone())?; + if let Some((header_name, header_value)) = maybe_header.as_ref() { + builder = builder.header(header_name, header_value); + } + let mut response = builder.send().await?; let status = response.status(); if status.is_redirection() { for _ in 0..5 { let new_url = resolve_redirect_from_response(&url, &response)?; - let new_response = - self.get_no_redirect(new_url.clone())?.send().await?; + let mut builder = self.get_no_redirect(new_url.clone())?; + + if new_url.origin() == url.origin() { + if let Some((header_name, header_value)) = maybe_header.as_ref() { + builder = builder.header(header_name, header_value); + } + } else { + maybe_header = None; + } + + let new_response = builder.send().await?; let status = new_response.status(); if status.is_redirection() { response = new_response; diff --git a/cli/lsp/config.rs b/cli/lsp/config.rs index 597f45688cabf0..89ecb84d8593d6 100644 --- a/cli/lsp/config.rs +++ b/cli/lsp/config.rs @@ -24,6 +24,7 @@ use deno_core::serde_json::json; use deno_core::serde_json::Value; use deno_core::ModuleSpecifier; use deno_lockfile::Lockfile; +use deno_npm::npm_rc::ResolvedNpmRc; use deno_runtime::deno_node::PackageJson; use deno_runtime::fs_util::specifier_to_file_path; use deno_runtime::permissions::PermissionsContainer; @@ -1090,6 +1091,7 @@ pub struct ConfigData { pub vendor_dir: Option, pub lockfile: Option>>, pub package_json: Option>, + pub npmrc: Option>, pub import_map: Option>, pub import_map_from_settings: bool, watched_files: HashMap, @@ -1274,6 +1276,8 @@ impl ConfigData { // Load package.json let mut package_json = None; + // TODO(bartlomieju): support discovering .npmrc + let npmrc = None; if let Ok(path) = specifier_to_file_path(scope) { let path = path.join("package.json"); if let Ok(specifier) = ModuleSpecifier::from_file_path(&path) { @@ -1429,6 +1433,7 @@ impl ConfigData { vendor_dir, lockfile: lockfile.map(Mutex::new).map(Arc::new), package_json: package_json.map(Arc::new), + npmrc: npmrc.map(Arc::new), import_map: import_map.map(Arc::new), import_map_from_settings, watched_files, diff --git a/cli/lsp/language_server.rs b/cli/lsp/language_server.rs index 009b466548344f..444597ff2f9572 100644 --- a/cli/lsp/language_server.rs +++ b/cli/lsp/language_server.rs @@ -83,6 +83,7 @@ use super::tsc::ChangeKind; use super::tsc::GetCompletionDetailsArgs; use super::tsc::TsServer; use super::urls; +use crate::args::create_default_npmrc; use crate::args::get_root_cert_store; use crate::args::CaData; use crate::args::CacheSetting; @@ -3299,6 +3300,9 @@ impl Inner { config_data.and_then(|d| d.config_file.as_deref().cloned()), config_data.and_then(|d| d.lockfile.clone()), config_data.and_then(|d| d.package_json.as_deref().cloned()), + config_data + .and_then(|d| d.npmrc.clone()) + .unwrap_or_else(create_default_npmrc), force_global_cache, )?; diff --git a/cli/lsp/resolver.rs b/cli/lsp/resolver.rs index 27c4f6acf76e13..18c886b5bc7b92 100644 --- a/cli/lsp/resolver.rs +++ b/cli/lsp/resolver.rs @@ -1,5 +1,6 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +use crate::args::create_default_npmrc; use crate::args::package_json; use crate::args::CacheSetting; use crate::cache::FastInsecureHasher; @@ -337,7 +338,10 @@ async fn create_npm_resolver( // do not install while resolving in the lsp—leave that to the cache command package_json_installer: CliNpmResolverManagedPackageJsonInstallerOption::NoInstall, - npm_registry_url: crate::args::npm_registry_url().to_owned(), + npmrc: config_data + .npmrc + .clone() + .unwrap_or_else(create_default_npmrc), npm_system_info: NpmSystemInfo::default(), }) }; diff --git a/cli/npm/cache_dir.rs b/cli/npm/cache_dir.rs index 1c28a9b81edfdf..d519137757e2ff 100644 --- a/cli/npm/cache_dir.rs +++ b/cli/npm/cache_dir.rs @@ -20,10 +20,13 @@ pub struct NpmCacheDir { root_dir: PathBuf, // cached url representation of the root directory root_dir_url: Url, + // A list of all registry that were discovered via `.npmrc` files + // turned into a safe directory names. + known_registries_dirnames: Vec, } impl NpmCacheDir { - pub fn new(root_dir: PathBuf) -> Self { + pub fn new(root_dir: PathBuf, known_registries_urls: Vec) -> Self { fn try_get_canonicalized_root_dir( root_dir: &Path, ) -> Result { @@ -38,12 +41,27 @@ impl NpmCacheDir { let root_dir = try_get_canonicalized_root_dir(&root_dir).unwrap_or(root_dir); let root_dir_url = Url::from_directory_path(&root_dir).unwrap(); + + let known_registries_dirnames: Vec<_> = known_registries_urls + .into_iter() + .map(|url| { + root_url_to_safe_local_dirname(&url) + .to_string_lossy() + .replace('\\', "/") + }) + .collect(); + Self { root_dir, root_dir_url, + known_registries_dirnames, } } + pub fn root_dir(&self) -> &Path { + &self.root_dir + } + pub fn root_dir_url(&self) -> &Url { &self.root_dir_url } @@ -88,7 +106,7 @@ impl NpmCacheDir { } } - pub fn registry_folder(&self, registry_url: &Url) -> PathBuf { + fn registry_folder(&self, registry_url: &Url) -> PathBuf { self .root_dir .join(root_url_to_safe_local_dirname(registry_url)) @@ -97,23 +115,32 @@ impl NpmCacheDir { pub fn resolve_package_folder_id_from_specifier( &self, specifier: &ModuleSpecifier, - registry_url: &Url, ) -> Option { - let registry_root_dir = self - .root_dir_url - .join(&format!( - "{}/", - root_url_to_safe_local_dirname(registry_url) - .to_string_lossy() - .replace('\\', "/") - )) - // this not succeeding indicates a fatal issue, so unwrap - .unwrap(); - let mut relative_url = registry_root_dir.make_relative(specifier)?; - if relative_url.starts_with("../") { - return None; + let mut maybe_relative_url = None; + + // Iterate through known registries and try to get a match. + for registry_dirname in &self.known_registries_dirnames { + let registry_root_dir = self + .root_dir_url + .join(&format!("{}/", registry_dirname)) + // this not succeeding indicates a fatal issue, so unwrap + .unwrap(); + + let Some(relative_url) = registry_root_dir.make_relative(specifier) + else { + continue; + }; + + if relative_url.starts_with("../") { + continue; + } + + maybe_relative_url = Some(relative_url); + break; } + let mut relative_url = maybe_relative_url?; + // base32 decode the url if it starts with an underscore // * Ex. _{base32(package_name)}/ if let Some(end_url) = relative_url.strip_prefix('_') { @@ -194,8 +221,8 @@ mod test { fn should_get_package_folder() { let deno_dir = crate::cache::DenoDir::new(None).unwrap(); let root_dir = deno_dir.npm_folder_path(); - let cache = NpmCacheDir::new(root_dir.clone()); let registry_url = Url::parse("https://registry.npmjs.org/").unwrap(); + let cache = NpmCacheDir::new(root_dir.clone(), vec![registry_url.clone()]); assert_eq!( cache.package_folder_for_id( diff --git a/cli/npm/common.rs b/cli/npm/common.rs index e409248382e9d7..bf45aa5b816a67 100644 --- a/cli/npm/common.rs +++ b/cli/npm/common.rs @@ -1,5 +1,8 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +use deno_npm::npm_rc::RegistryConfig; +use reqwest::header; + /// Gets the corresponding @types package for the provided package name. pub fn types_package_name(package_name: &str) -> String { debug_assert!(!package_name.starts_with("@types/")); @@ -8,6 +11,27 @@ pub fn types_package_name(package_name: &str) -> String { format!("@types/{}", package_name.replace('/', "__")) } +// TODO(bartlomieju): support more auth methods besides token and basic auth +pub fn maybe_auth_header_for_npm_registry( + registry_config: &RegistryConfig, +) -> Option<(header::HeaderName, header::HeaderValue)> { + if let Some(token) = registry_config.auth_token.as_ref() { + return Some(( + header::AUTHORIZATION, + header::HeaderValue::from_str(&format!("Bearer {}", token)).unwrap(), + )); + } + + if let Some(auth) = registry_config.auth.as_ref() { + return Some(( + header::AUTHORIZATION, + header::HeaderValue::from_str(&format!("Basic {}", auth)).unwrap(), + )); + } + + None +} + #[cfg(test)] mod test { use super::types_package_name; diff --git a/cli/npm/managed/cache.rs b/cli/npm/managed/cache.rs index 44b98fcee0b0d8..4056c97ad9ed16 100644 --- a/cli/npm/managed/cache.rs +++ b/cli/npm/managed/cache.rs @@ -13,6 +13,7 @@ use deno_core::error::custom_error; use deno_core::error::AnyError; use deno_core::parking_lot::Mutex; use deno_core::url::Url; +use deno_npm::npm_rc::ResolvedNpmRc; use deno_npm::registry::NpmPackageVersionDistInfo; use deno_npm::NpmPackageCacheFolderId; use deno_runtime::deno_fs; @@ -20,6 +21,7 @@ use deno_semver::package::PackageNv; use crate::args::CacheSetting; use crate::http_util::HttpClient; +use crate::npm::common::maybe_auth_header_for_npm_registry; use crate::npm::NpmCacheDir; use crate::util::fs::hard_link_dir_recursive; use crate::util::progress_bar::ProgressBar; @@ -35,6 +37,7 @@ pub struct NpmCache { fs: Arc, http_client: Arc, progress_bar: ProgressBar, + pub(crate) npmrc: Arc, /// ensures a package is only downloaded once per run previously_reloaded_packages: Mutex>, } @@ -46,6 +49,7 @@ impl NpmCache { fs: Arc, http_client: Arc, progress_bar: ProgressBar, + npmrc: Arc, ) -> Self { Self { cache_dir, @@ -54,6 +58,7 @@ impl NpmCache { http_client, progress_bar, previously_reloaded_packages: Default::default(), + npmrc, } } @@ -82,10 +87,9 @@ impl NpmCache { &self, package: &PackageNv, dist: &NpmPackageVersionDistInfo, - registry_url: &Url, ) -> Result<(), AnyError> { self - .ensure_package_inner(package, dist, registry_url) + .ensure_package_inner(package, dist) .await .with_context(|| format!("Failed caching npm package '{package}'.")) } @@ -94,8 +98,10 @@ impl NpmCache { &self, package_nv: &PackageNv, dist: &NpmPackageVersionDistInfo, - registry_url: &Url, ) -> Result<(), AnyError> { + let registry_url = self.npmrc.get_registry_url(&package_nv.name); + let registry_config = self.npmrc.get_registry_config(&package_nv.name); + let package_folder = self .cache_dir .package_folder_for_name_and_version(package_nv, registry_url); @@ -118,10 +124,12 @@ impl NpmCache { bail!("Tarball URL was empty."); } + let maybe_auth_header = maybe_auth_header_for_npm_registry(registry_config); + let guard = self.progress_bar.update(&dist.tarball); let maybe_bytes = self .http_client - .download_with_progress(&dist.tarball, &guard) + .download_with_progress(&dist.tarball, maybe_auth_header, &guard) .await?; match maybe_bytes { Some(bytes) => { @@ -164,8 +172,8 @@ impl NpmCache { pub fn ensure_copy_package( &self, folder_id: &NpmPackageCacheFolderId, - registry_url: &Url, ) -> Result<(), AnyError> { + let registry_url = self.npmrc.get_registry_url(&folder_id.nv.name); assert_ne!(folder_id.copy_index, 0); let package_folder = self .cache_dir @@ -192,40 +200,37 @@ impl NpmCache { Ok(()) } - pub fn package_folder_for_id( - &self, - id: &NpmPackageCacheFolderId, - registry_url: &Url, - ) -> PathBuf { + pub fn package_folder_for_id(&self, id: &NpmPackageCacheFolderId) -> PathBuf { + let registry_url = self.npmrc.get_registry_url(&id.nv.name); self.cache_dir.package_folder_for_id(id, registry_url) } pub fn package_folder_for_name_and_version( &self, package: &PackageNv, - registry_url: &Url, ) -> PathBuf { + let registry_url = self.npmrc.get_registry_url(&package.name); self .cache_dir .package_folder_for_name_and_version(package, registry_url) } - pub fn package_name_folder(&self, name: &str, registry_url: &Url) -> PathBuf { + pub fn package_name_folder(&self, name: &str) -> PathBuf { + let registry_url = self.npmrc.get_registry_url(name); self.cache_dir.package_name_folder(name, registry_url) } - pub fn registry_folder(&self, registry_url: &Url) -> PathBuf { - self.cache_dir.registry_folder(registry_url) + pub fn root_folder(&self) -> PathBuf { + self.cache_dir.root_dir().to_owned() } pub fn resolve_package_folder_id_from_specifier( &self, specifier: &ModuleSpecifier, - registry_url: &Url, ) -> Option { self .cache_dir - .resolve_package_folder_id_from_specifier(specifier, registry_url) + .resolve_package_folder_id_from_specifier(specifier) } } diff --git a/cli/npm/managed/mod.rs b/cli/npm/managed/mod.rs index 0625911d349d74..3a2657cfb4638d 100644 --- a/cli/npm/managed/mod.rs +++ b/cli/npm/managed/mod.rs @@ -9,8 +9,8 @@ use deno_core::anyhow::Context; use deno_core::error::AnyError; use deno_core::parking_lot::Mutex; use deno_core::serde_json; -use deno_core::url::Url; use deno_graph::NpmPackageReqResolution; +use deno_npm::npm_rc::ResolvedNpmRc; use deno_npm::registry::NpmRegistryApi; use deno_npm::resolution::NpmResolutionSnapshot; use deno_npm::resolution::PackageReqNotFoundError; @@ -72,7 +72,7 @@ pub struct CliNpmResolverManagedCreateOptions { pub maybe_node_modules_path: Option, pub npm_system_info: NpmSystemInfo, pub package_json_installer: CliNpmResolverManagedPackageJsonInstallerOption, - pub npm_registry_url: Url, + pub npmrc: Arc, } pub async fn create_managed_npm_resolver_for_lsp( @@ -96,7 +96,6 @@ pub async fn create_managed_npm_resolver_for_lsp( options.text_only_progress_bar, options.maybe_node_modules_path, options.package_json_installer, - options.npm_registry_url, options.npm_system_info, ) } @@ -116,7 +115,6 @@ pub async fn create_managed_npm_resolver( options.text_only_progress_bar, options.maybe_node_modules_path, options.package_json_installer, - options.npm_registry_url, options.npm_system_info, )) } @@ -131,7 +129,6 @@ fn create_inner( text_only_progress_bar: crate::util::progress_bar::ProgressBar, node_modules_dir_path: Option, package_json_installer: CliNpmResolverManagedPackageJsonInstallerOption, - npm_registry_url: Url, npm_system_info: NpmSystemInfo, ) -> Arc { let resolution = Arc::new(NpmResolution::from_serialized( @@ -143,7 +140,6 @@ fn create_inner( fs.clone(), npm_cache.clone(), &text_only_progress_bar, - npm_registry_url, resolution.clone(), node_modules_dir_path, npm_system_info.clone(), @@ -175,11 +171,15 @@ fn create_inner( fn create_cache(options: &CliNpmResolverManagedCreateOptions) -> Arc { Arc::new(NpmCache::new( - NpmCacheDir::new(options.npm_global_cache_dir.clone()), + NpmCacheDir::new( + options.npm_global_cache_dir.clone(), + options.npmrc.get_all_known_registries_urls(), + ), options.cache_setting.clone(), options.fs.clone(), options.http_client.clone(), options.text_only_progress_bar.clone(), + options.npmrc.clone(), )) } @@ -188,9 +188,9 @@ fn create_api( npm_cache: Arc, ) -> Arc { Arc::new(CliNpmRegistryApi::new( - options.npm_registry_url.clone(), npm_cache.clone(), options.http_client.clone(), + options.npmrc.clone(), options.text_only_progress_bar.clone(), )) } @@ -483,15 +483,8 @@ impl ManagedCliNpmResolver { .map_err(|err| err.into()) } - pub fn registry_base_url(&self) -> &ModuleSpecifier { - self.api.base_url() - } - - pub fn registry_folder_in_global_cache( - &self, - registry_url: &ModuleSpecifier, - ) -> PathBuf { - self.global_npm_cache.registry_folder(registry_url) + pub fn global_cache_root_folder(&self) -> PathBuf { + self.global_npm_cache.root_folder() } } @@ -564,7 +557,6 @@ impl CliNpmResolver for ManagedCliNpmResolver { self.fs.clone(), self.global_npm_cache.clone(), &self.progress_bar, - self.api.base_url().clone(), npm_resolution, self.root_node_modules_path().map(ToOwned::to_owned), self.npm_system_info.clone(), diff --git a/cli/npm/managed/registry.rs b/cli/npm/managed/registry.rs index 3199a3c32849f5..391ff06401cd1d 100644 --- a/cli/npm/managed/registry.rs +++ b/cli/npm/managed/registry.rs @@ -18,6 +18,8 @@ use deno_core::futures::FutureExt; use deno_core::parking_lot::Mutex; use deno_core::serde_json; use deno_core::url::Url; +use deno_npm::npm_rc::RegistryConfig; +use deno_npm::npm_rc::ResolvedNpmRc; use deno_npm::registry::NpmPackageInfo; use deno_npm::registry::NpmRegistryApi; use deno_npm::registry::NpmRegistryPackageInfoLoadError; @@ -25,6 +27,7 @@ use deno_npm::registry::NpmRegistryPackageInfoLoadError; use crate::args::CacheSetting; use crate::cache::CACHE_PERM; use crate::http_util::HttpClient; +use crate::npm::common::maybe_auth_header_for_npm_registry; use crate::util::fs::atomic_write_file; use crate::util::progress_bar::ProgressBar; use crate::util::sync::AtomicFlag; @@ -36,17 +39,17 @@ pub struct CliNpmRegistryApi(Option>); impl CliNpmRegistryApi { pub fn new( - base_url: Url, cache: Arc, http_client: Arc, + npmrc: Arc, progress_bar: ProgressBar, ) -> Self { Self(Some(Arc::new(CliNpmRegistryApiInner { - base_url, cache, force_reload_flag: Default::default(), mem_cache: Default::default(), previously_reloaded_packages: Default::default(), + npmrc, http_client, progress_bar, }))) @@ -64,10 +67,6 @@ impl CliNpmRegistryApi { self.inner().get_cached_package_info(name) } - pub fn base_url(&self) -> &Url { - &self.inner().base_url - } - fn inner(&self) -> &Arc { // this panicking indicates a bug in the code where this // wasn't initialized @@ -121,12 +120,12 @@ enum CacheItem { #[derive(Debug)] struct CliNpmRegistryApiInner { - base_url: Url, cache: Arc, force_reload_flag: AtomicFlag, mem_cache: Mutex>, previously_reloaded_packages: Mutex>, http_client: Arc, + npmrc: Arc, progress_bar: ProgressBar, } @@ -273,13 +272,20 @@ impl CliNpmRegistryApiInner { &self, name: &str, ) -> Result, AnyError> { + let registry_url = self.npmrc.get_registry_url(name); + let registry_config = self.npmrc.get_registry_config(name); + self - .load_package_info_from_registry_inner(name) + .load_package_info_from_registry_inner( + name, + registry_url, + registry_config, + ) .await .with_context(|| { format!( "Error getting response at {} for package \"{}\"", - self.get_package_url(name), + self.get_package_url(name, registry_url), name ) }) @@ -288,6 +294,8 @@ impl CliNpmRegistryApiInner { async fn load_package_info_from_registry_inner( &self, name: &str, + registry_url: &Url, + registry_config: &RegistryConfig, ) -> Result, AnyError> { if *self.cache.cache_setting() == CacheSetting::Only { return Err(custom_error( @@ -298,12 +306,14 @@ impl CliNpmRegistryApiInner { )); } - let package_url = self.get_package_url(name); + let package_url = self.get_package_url(name, registry_url); let guard = self.progress_bar.update(package_url.as_str()); + let maybe_auth_header = maybe_auth_header_for_npm_registry(registry_config); + let maybe_bytes = self .http_client - .download_with_progress(package_url, &guard) + .download_with_progress(package_url, maybe_auth_header, &guard) .await?; match maybe_bytes { Some(bytes) => { @@ -315,7 +325,7 @@ impl CliNpmRegistryApiInner { } } - fn get_package_url(&self, name: &str) -> Url { + fn get_package_url(&self, name: &str, registry_url: &Url) -> Url { // list of all characters used in npm packages: // !, ', (, ), *, -, ., /, [0-9], @, [A-Za-z], _, ~ const ASCII_SET: percent_encoding::AsciiSet = @@ -332,11 +342,11 @@ impl CliNpmRegistryApiInner { .remove(b'_') .remove(b'~'); let name = percent_encoding::utf8_percent_encode(name, &ASCII_SET); - self.base_url.join(&name.to_string()).unwrap() + registry_url.join(&name.to_string()).unwrap() } fn get_package_file_cache_path(&self, name: &str) -> PathBuf { - let name_folder_path = self.cache.package_name_folder(name, &self.base_url); + let name_folder_path = self.cache.package_name_folder(name); name_folder_path.join("registry.json") } diff --git a/cli/npm/managed/resolvers/common.rs b/cli/npm/managed/resolvers/common.rs index a326b562bbd72f..b010bdd7c9851c 100644 --- a/cli/npm/managed/resolvers/common.rs +++ b/cli/npm/managed/resolvers/common.rs @@ -44,11 +44,6 @@ pub trait NpmPackageFsResolver: Send + Sync { mode: NodeResolutionMode, ) -> Result; - fn resolve_package_folder_from_specifier( - &self, - specifier: &ModuleSpecifier, - ) -> Result, AnyError>; - fn resolve_package_cache_folder_id_from_specifier( &self, specifier: &ModuleSpecifier, @@ -132,16 +127,12 @@ impl RegistryReadPermissionChecker { pub async fn cache_packages( packages: Vec, cache: &Arc, - registry_url: &Url, ) -> Result<(), AnyError> { let mut handles = Vec::with_capacity(packages.len()); for package in packages { let cache = cache.clone(); - let registry_url = registry_url.clone(); let handle = spawn(async move { - cache - .ensure_package(&package.id.nv, &package.dist, ®istry_url) - .await + cache.ensure_package(&package.id.nv, &package.dist).await }); handles.push(handle); } diff --git a/cli/npm/managed/resolvers/global.rs b/cli/npm/managed/resolvers/global.rs index 4b3c9d6131cdbd..cfc57e5913e1ca 100644 --- a/cli/npm/managed/resolvers/global.rs +++ b/cli/npm/managed/resolvers/global.rs @@ -32,7 +32,6 @@ use super::common::RegistryReadPermissionChecker; pub struct GlobalNpmPackageResolver { cache: Arc, resolution: Arc, - registry_url: Url, system_info: NpmSystemInfo, registry_read_permission_checker: RegistryReadPermissionChecker, } @@ -41,18 +40,16 @@ impl GlobalNpmPackageResolver { pub fn new( fs: Arc, cache: Arc, - registry_url: Url, resolution: Arc, system_info: NpmSystemInfo, ) -> Self { Self { cache: cache.clone(), resolution, - registry_url: registry_url.clone(), system_info, registry_read_permission_checker: RegistryReadPermissionChecker::new( fs, - cache.registry_folder(®istry_url), + cache.root_folder(), ), } } @@ -84,11 +81,7 @@ impl NpmPackageFsResolver for GlobalNpmPackageResolver { .resolution .resolve_pkg_cache_folder_id_from_pkg_id(id) .unwrap(); - Ok( - self - .cache - .package_folder_for_id(&folder_id, &self.registry_url), - ) + Ok(self.cache.package_folder_for_id(&folder_id)) } fn resolve_package_folder_from_package( @@ -99,7 +92,7 @@ impl NpmPackageFsResolver for GlobalNpmPackageResolver { ) -> Result { let Some(referrer_pkg_id) = self .cache - .resolve_package_folder_id_from_specifier(referrer, &self.registry_url) + .resolve_package_folder_id_from_specifier(referrer) else { bail!("could not find npm package for '{}'", referrer); }; @@ -119,32 +112,14 @@ impl NpmPackageFsResolver for GlobalNpmPackageResolver { self.package_folder(&pkg.id) } - fn resolve_package_folder_from_specifier( - &self, - specifier: &ModuleSpecifier, - ) -> Result, AnyError> { - let Some(pkg_folder_id) = self - .cache - .resolve_package_folder_id_from_specifier(specifier, &self.registry_url) - else { - return Ok(None); - }; - Ok(Some( - self - .cache - .package_folder_for_id(&pkg_folder_id, &self.registry_url), - )) - } - fn resolve_package_cache_folder_id_from_specifier( &self, specifier: &ModuleSpecifier, ) -> Result, AnyError> { Ok( - self.cache.resolve_package_folder_id_from_specifier( - specifier, - &self.registry_url, - ), + self + .cache + .resolve_package_folder_id_from_specifier(specifier), ) } @@ -153,19 +128,13 @@ impl NpmPackageFsResolver for GlobalNpmPackageResolver { .resolution .all_system_packages_partitioned(&self.system_info); - cache_packages( - package_partitions.packages, - &self.cache, - &self.registry_url, - ) - .await?; + cache_packages(package_partitions.packages, &self.cache).await?; // create the copy package folders for copy in package_partitions.copy_packages { - self.cache.ensure_copy_package( - ©.get_package_cache_folder_id(), - &self.registry_url, - )?; + self + .cache + .ensure_copy_package(©.get_package_cache_folder_id())?; } Ok(()) diff --git a/cli/npm/managed/resolvers/local.rs b/cli/npm/managed/resolvers/local.rs index f8d69d1482e54a..9d0ca3f8c5a075 100644 --- a/cli/npm/managed/resolvers/local.rs +++ b/cli/npm/managed/resolvers/local.rs @@ -58,7 +58,6 @@ pub struct LocalNpmPackageResolver { cache: Arc, progress_bar: ProgressBar, resolution: Arc, - registry_url: Url, root_node_modules_path: PathBuf, root_node_modules_url: Url, system_info: NpmSystemInfo, @@ -70,7 +69,6 @@ impl LocalNpmPackageResolver { fs: Arc, cache: Arc, progress_bar: ProgressBar, - registry_url: Url, node_modules_folder: PathBuf, resolution: Arc, system_info: NpmSystemInfo, @@ -80,7 +78,6 @@ impl LocalNpmPackageResolver { cache, progress_bar, resolution, - registry_url, root_node_modules_url: Url::from_directory_path(&node_modules_folder) .unwrap(), root_node_modules_path: node_modules_folder.clone(), @@ -126,6 +123,17 @@ impl LocalNpmPackageResolver { .map(Some) .map_err(|err| err.into()) } + + fn resolve_package_folder_from_specifier( + &self, + specifier: &ModuleSpecifier, + ) -> Result, AnyError> { + let Some(local_path) = self.resolve_folder_for_specifier(specifier)? else { + return Ok(None); + }; + let package_root_path = self.resolve_package_root(&local_path); + Ok(Some(package_root_path)) + } } #[async_trait] @@ -202,17 +210,6 @@ impl NpmPackageFsResolver for LocalNpmPackageResolver { ); } - fn resolve_package_folder_from_specifier( - &self, - specifier: &ModuleSpecifier, - ) -> Result, AnyError> { - let Some(local_path) = self.resolve_folder_for_specifier(specifier)? else { - return Ok(None); - }; - let package_root_path = self.resolve_package_root(&local_path); - Ok(Some(package_root_path)) - } - fn resolve_package_cache_folder_id_from_specifier( &self, specifier: &ModuleSpecifier, @@ -231,7 +228,6 @@ impl NpmPackageFsResolver for LocalNpmPackageResolver { &self.resolution.snapshot(), &self.cache, &self.progress_bar, - &self.registry_url, &self.root_node_modules_path, &self.system_info, ) @@ -254,7 +250,6 @@ async fn sync_resolution_with_fs( snapshot: &NpmResolutionSnapshot, cache: &Arc, progress_bar: &ProgressBar, - registry_url: &Url, root_node_modules_dir_path: &Path, system_info: &NpmSystemInfo, ) -> Result<(), AnyError> { @@ -317,12 +312,9 @@ async fn sync_resolution_with_fs( let pb = progress_bar.clone(); let cache = cache.clone(); - let registry_url = registry_url.clone(); let package = package.clone(); let handle = spawn(async move { - cache - .ensure_package(&package.id.nv, &package.dist, ®istry_url) - .await?; + cache.ensure_package(&package.id.nv, &package.dist).await?; let pb_guard = pb.update_with_prompt( ProgressMessagePrompt::Initialize, &package.id.nv.to_string(), @@ -332,8 +324,8 @@ async fn sync_resolution_with_fs( join_package_name(&sub_node_modules, &package.id.nv.name); fs::create_dir_all(&package_path) .with_context(|| format!("Creating '{}'", folder_path.display()))?; - let cache_folder = cache - .package_folder_for_name_and_version(&package.id.nv, ®istry_url); + let cache_folder = + cache.package_folder_for_name_and_version(&package.id.nv); if hard_link_dir_recursive(&cache_folder, &package_path).is_err() { // Fallback to copying the directory. // diff --git a/cli/npm/managed/resolvers/mod.rs b/cli/npm/managed/resolvers/mod.rs index dfd291afc287da..d5472344ab56eb 100644 --- a/cli/npm/managed/resolvers/mod.rs +++ b/cli/npm/managed/resolvers/mod.rs @@ -7,7 +7,6 @@ mod local; use std::path::PathBuf; use std::sync::Arc; -use deno_core::url::Url; use deno_npm::NpmSystemInfo; use deno_runtime::deno_fs::FileSystem; @@ -25,7 +24,6 @@ pub fn create_npm_fs_resolver( fs: Arc, cache: Arc, progress_bar: &ProgressBar, - registry_url: Url, resolution: Arc, maybe_node_modules_path: Option, system_info: NpmSystemInfo, @@ -35,7 +33,6 @@ pub fn create_npm_fs_resolver( fs, cache, progress_bar.clone(), - registry_url, node_modules_folder, resolution, system_info, @@ -43,7 +40,6 @@ pub fn create_npm_fs_resolver( None => Arc::new(GlobalNpmPackageResolver::new( fs, cache, - registry_url, resolution, system_info, )), diff --git a/cli/standalone/binary.rs b/cli/standalone/binary.rs index 143c1824daec04..042d3c3c6cfd33 100644 --- a/cli/standalone/binary.rs +++ b/cli/standalone/binary.rs @@ -537,7 +537,7 @@ impl<'a> DenoCompileBinaryWriter<'a> { self .client - .download_with_progress(download_url, &progress) + .download_with_progress(download_url, None, &progress) .await? }; let bytes = match maybe_bytes { @@ -670,9 +670,7 @@ impl<'a> DenoCompileBinaryWriter<'a> { } else { // DO NOT include the user's registry url as it may contain credentials, // but also don't make this dependent on the registry url - let registry_url = npm_resolver.registry_base_url(); - let root_path = - npm_resolver.registry_folder_in_global_cache(registry_url); + let root_path = npm_resolver.global_cache_root_folder(); let mut builder = VfsBuilder::new(root_path)?; for package in npm_resolver.all_system_packages(&self.npm_system_info) { diff --git a/cli/standalone/mod.rs b/cli/standalone/mod.rs index 288618287935d8..1c081bc8692645 100644 --- a/cli/standalone/mod.rs +++ b/cli/standalone/mod.rs @@ -1,5 +1,6 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +use crate::args::create_default_npmrc; use crate::args::get_root_cert_store; use crate::args::npm_pkg_req_ref_to_binary_command; use crate::args::CaData; @@ -349,7 +350,8 @@ pub async fn run( let root_path = std::env::temp_dir() .join(format!("deno-compile-{}", current_exe_name)) .join("node_modules"); - let npm_cache_dir = NpmCacheDir::new(root_path.clone()); + let npm_cache_dir = + NpmCacheDir::new(root_path.clone(), vec![npm_registry_url.clone()]); let npm_global_cache_dir = npm_cache_dir.get_cache_location(); let cache_setting = CacheSetting::Only; let (package_json_deps_provider, fs, npm_resolver, maybe_vfs_root) = @@ -363,7 +365,7 @@ pub async fn run( let vfs_root_dir_path = if node_modules_dir { root_path } else { - npm_cache_dir.registry_folder(&npm_registry_url) + npm_cache_dir.root_dir().to_owned() }; let vfs = load_npm_vfs(vfs_root_dir_path.clone()) .context("Failed to load npm vfs.")?; @@ -392,8 +394,10 @@ pub async fn run( CliNpmResolverManagedPackageJsonInstallerOption::ConditionalInstall( package_json_deps_provider.clone(), ), - npm_registry_url, npm_system_info: Default::default(), + // Packages from different registries are already inlined in the ESZip, + // so no need to create actual `.npmrc` configuration. + npmrc: create_default_npmrc(), }), ) .await?; @@ -448,8 +452,10 @@ pub async fn run( CliNpmResolverManagedPackageJsonInstallerOption::ConditionalInstall( package_json_deps_provider.clone(), ), - npm_registry_url, npm_system_info: Default::default(), + // Packages from different registries are already inlined in the ESZip, + // so no need to create actual `.npmrc` configuration. + npmrc: create_default_npmrc(), }), ) .await?; diff --git a/cli/tools/installer.rs b/cli/tools/installer.rs index b13dea6fd90db4..b8ee012207ce63 100644 --- a/cli/tools/installer.rs +++ b/cli/tools/installer.rs @@ -139,7 +139,7 @@ pub async fn infer_name_from_url(url: &Url) -> Option { if url.path() == "/" { let client = HttpClient::new(None, None); - if let Ok(res) = client.get_redirected_response(url.clone()).await { + if let Ok(res) = client.get_redirected_response(url.clone(), None).await { url = res.url().clone(); } } diff --git a/cli/tools/upgrade.rs b/cli/tools/upgrade.rs index 073ebdc1c932db..b3f06e55e3a4fe 100644 --- a/cli/tools/upgrade.rs +++ b/cli/tools/upgrade.rs @@ -620,7 +620,7 @@ async fn download_package( // text above which will stay alive after the progress bars are complete let progress = progress_bar.update(""); client - .download_with_progress(download_url, &progress) + .download_with_progress(download_url, None, &progress) .await? }; match maybe_bytes { diff --git a/tests/integration/npm_tests.rs b/tests/integration/npm_tests.rs index 4c4868c6522fb5..b26de619a89e74 100644 --- a/tests/integration/npm_tests.rs +++ b/tests/integration/npm_tests.rs @@ -3087,7 +3087,7 @@ async fn test_private_npm_registry() { let client = reqwest::Client::new(); - let url = Url::parse("http://127.0.0.1:4261/@denotest2/basic").unwrap(); + let url = Url::parse("http://127.0.0.1:4261/@denotest/basic").unwrap(); let req = reqwest::Request::new(reqwest::Method::GET, url.clone()); let resp = client.execute(req).await.unwrap(); diff --git a/tests/registry/npm-private/@denotest2/basic/1.0.0/main.d.mts b/tests/registry/npm-private/@denotest/basic/1.0.0/main.d.mts similarity index 100% rename from tests/registry/npm-private/@denotest2/basic/1.0.0/main.d.mts rename to tests/registry/npm-private/@denotest/basic/1.0.0/main.d.mts diff --git a/tests/registry/npm-private/@denotest2/basic/1.0.0/main.mjs b/tests/registry/npm-private/@denotest/basic/1.0.0/main.mjs similarity index 100% rename from tests/registry/npm-private/@denotest2/basic/1.0.0/main.mjs rename to tests/registry/npm-private/@denotest/basic/1.0.0/main.mjs diff --git a/tests/registry/npm-private/@denotest2/basic/1.0.0/other.mjs b/tests/registry/npm-private/@denotest/basic/1.0.0/other.mjs similarity index 100% rename from tests/registry/npm-private/@denotest2/basic/1.0.0/other.mjs rename to tests/registry/npm-private/@denotest/basic/1.0.0/other.mjs diff --git a/tests/registry/npm-private/@denotest/basic/1.0.0/package.json b/tests/registry/npm-private/@denotest/basic/1.0.0/package.json new file mode 100644 index 00000000000000..8f7324aa53a6d2 --- /dev/null +++ b/tests/registry/npm-private/@denotest/basic/1.0.0/package.json @@ -0,0 +1,7 @@ +{ + "name": "@denotest/basic", + "version": "1.0.0", + "type": "module", + "main": "main.mjs", + "types": "main.d.mts" +} diff --git a/tests/registry/npm-private2/@denotest2/basic/1.0.0/main.d.mts b/tests/registry/npm-private2/@denotest2/basic/1.0.0/main.d.mts new file mode 100644 index 00000000000000..29da1e6d7b2ef4 --- /dev/null +++ b/tests/registry/npm-private2/@denotest2/basic/1.0.0/main.d.mts @@ -0,0 +1,3 @@ +export declare function setValue(val: number): void; +export declare function getValue(): number; +export declare const url: string; diff --git a/tests/registry/npm-private2/@denotest2/basic/1.0.0/main.mjs b/tests/registry/npm-private2/@denotest2/basic/1.0.0/main.mjs new file mode 100644 index 00000000000000..0a44f75859e9b0 --- /dev/null +++ b/tests/registry/npm-private2/@denotest2/basic/1.0.0/main.mjs @@ -0,0 +1,11 @@ +let value = 0; + +export function setValue(newValue) { + value = newValue; +} + +export function getValue() { + return value; +} + +export const url = import.meta.url; diff --git a/tests/registry/npm-private2/@denotest2/basic/1.0.0/other.mjs b/tests/registry/npm-private2/@denotest2/basic/1.0.0/other.mjs new file mode 100644 index 00000000000000..00ed99da45d6da --- /dev/null +++ b/tests/registry/npm-private2/@denotest2/basic/1.0.0/other.mjs @@ -0,0 +1,3 @@ +export function hello() { + return "hello, world!"; +} \ No newline at end of file diff --git a/tests/registry/npm-private/@denotest2/basic/1.0.0/package.json b/tests/registry/npm-private2/@denotest2/basic/1.0.0/package.json similarity index 100% rename from tests/registry/npm-private/@denotest2/basic/1.0.0/package.json rename to tests/registry/npm-private2/@denotest2/basic/1.0.0/package.json diff --git a/tests/specs/compile/npmrc/.npmrc b/tests/specs/compile/npmrc/.npmrc new file mode 100644 index 00000000000000..0624091f00304a --- /dev/null +++ b/tests/specs/compile/npmrc/.npmrc @@ -0,0 +1,4 @@ +@denotest:registry=http://127.0.0.1:4261/ +//127.0.0.1:4261/denotest/:_authToken=private-reg-token +@denotest2:registry=http://127.0.0.1:4262/ +//127.0.0.1:4262/denotest2/:_authToken=private-reg-token2 diff --git a/tests/specs/compile/npmrc/__test__.jsonc b/tests/specs/compile/npmrc/__test__.jsonc new file mode 100644 index 00000000000000..470e5299c856d6 --- /dev/null +++ b/tests/specs/compile/npmrc/__test__.jsonc @@ -0,0 +1,28 @@ +{ + "envs": { + "DENO_FUTURE": "1" + }, + "tempDir": true, + "steps": [{ + "args": "install", + "output": "install.out" + }, { + "if": "unix", + "args": "compile --output main main.js", + "output": "[WILDCARD]" + }, { + "if": "unix", + "commandName": "./main", + "args": [], + "output": "main.out" + }, { + "if": "windows", + "args": "compile --output main.exe main.js", + "output": "[WILDCARD]" + }, { + "if": "windows", + "commandName": "./main.exe", + "args": [], + "output": "main.out" + }] +} diff --git a/tests/specs/compile/npmrc/install.out b/tests/specs/compile/npmrc/install.out new file mode 100644 index 00000000000000..7484405db9dd8b --- /dev/null +++ b/tests/specs/compile/npmrc/install.out @@ -0,0 +1,9 @@ +⚠️ `deno install` behavior will change in Deno 2. To preserve the current behavior use the `-g` or `--global` flag. +[UNORDERED_START] +Download http://127.0.0.1:4261/@denotest/basic +Download http://127.0.0.1:4262/@denotest2/basic +Download http://localhost:4261/@denotest/basic/1.0.0.tgz +Download http://localhost:4262/@denotest2/basic/1.0.0.tgz +Initialize @denotest2/basic@1.0.0 +Initialize @denotest/basic@1.0.0 +[UNORDERED_END] diff --git a/tests/specs/compile/npmrc/main.js b/tests/specs/compile/npmrc/main.js new file mode 100644 index 00000000000000..66b39363600109 --- /dev/null +++ b/tests/specs/compile/npmrc/main.js @@ -0,0 +1,8 @@ +import { getValue, setValue } from "@denotest/basic"; +import * as test from "@denotest2/basic"; + +console.log(getValue()); +setValue(42); +console.log(getValue()); + +console.log(test.getValue()); diff --git a/tests/specs/compile/npmrc/main.out b/tests/specs/compile/npmrc/main.out new file mode 100644 index 00000000000000..bbe210bdbc6684 --- /dev/null +++ b/tests/specs/compile/npmrc/main.out @@ -0,0 +1,3 @@ +0 +42 +0 diff --git a/tests/specs/compile/npmrc/package.json b/tests/specs/compile/npmrc/package.json new file mode 100644 index 00000000000000..274d1ed7f437b8 --- /dev/null +++ b/tests/specs/compile/npmrc/package.json @@ -0,0 +1,8 @@ +{ + "name": "npmrc_test", + "version": "0.0.1", + "dependencies": { + "@denotest/basic": "1.0.0", + "@denotest2/basic": "1.0.0" + } +} diff --git a/tests/specs/npm/npmrc/.npmrc b/tests/specs/npm/npmrc/.npmrc new file mode 100644 index 00000000000000..085885ec8fdf81 --- /dev/null +++ b/tests/specs/npm/npmrc/.npmrc @@ -0,0 +1,4 @@ +@denotest:registry=http://127.0.0.1:4261/ +//127.0.0.1:4261/denotest/:_auth=ZGVubzpsYW5k +@denotest2:registry=http://127.0.0.1:4262/ +//127.0.0.1:4262/denotest2/:_auth=ZGVubzpsYW5kMg== diff --git a/tests/specs/npm/npmrc/__test__.jsonc b/tests/specs/npm/npmrc/__test__.jsonc new file mode 100644 index 00000000000000..44298ed2f5e07b --- /dev/null +++ b/tests/specs/npm/npmrc/__test__.jsonc @@ -0,0 +1,13 @@ +{ + "envs": { + "DENO_FUTURE": "1" + }, + "tempDir": true, + "steps": [{ + "args": "install", + "output": "install.out" + }, { + "args": "run -A main.js", + "output": "main.out" + }] +} diff --git a/tests/specs/npm/npmrc/install.out b/tests/specs/npm/npmrc/install.out new file mode 100644 index 00000000000000..7484405db9dd8b --- /dev/null +++ b/tests/specs/npm/npmrc/install.out @@ -0,0 +1,9 @@ +⚠️ `deno install` behavior will change in Deno 2. To preserve the current behavior use the `-g` or `--global` flag. +[UNORDERED_START] +Download http://127.0.0.1:4261/@denotest/basic +Download http://127.0.0.1:4262/@denotest2/basic +Download http://localhost:4261/@denotest/basic/1.0.0.tgz +Download http://localhost:4262/@denotest2/basic/1.0.0.tgz +Initialize @denotest2/basic@1.0.0 +Initialize @denotest/basic@1.0.0 +[UNORDERED_END] diff --git a/tests/specs/npm/npmrc/main.js b/tests/specs/npm/npmrc/main.js new file mode 100644 index 00000000000000..66b39363600109 --- /dev/null +++ b/tests/specs/npm/npmrc/main.js @@ -0,0 +1,8 @@ +import { getValue, setValue } from "@denotest/basic"; +import * as test from "@denotest2/basic"; + +console.log(getValue()); +setValue(42); +console.log(getValue()); + +console.log(test.getValue()); diff --git a/tests/specs/npm/npmrc/main.out b/tests/specs/npm/npmrc/main.out new file mode 100644 index 00000000000000..bbe210bdbc6684 --- /dev/null +++ b/tests/specs/npm/npmrc/main.out @@ -0,0 +1,3 @@ +0 +42 +0 diff --git a/tests/specs/npm/npmrc/package.json b/tests/specs/npm/npmrc/package.json new file mode 100644 index 00000000000000..274d1ed7f437b8 --- /dev/null +++ b/tests/specs/npm/npmrc/package.json @@ -0,0 +1,8 @@ +{ + "name": "npmrc_test", + "version": "0.0.1", + "dependencies": { + "@denotest/basic": "1.0.0", + "@denotest2/basic": "1.0.0" + } +} diff --git a/tests/specs/npm/npmrc_bad_token/.npmrc b/tests/specs/npm/npmrc_bad_token/.npmrc new file mode 100644 index 00000000000000..c4b03fd7aa891b --- /dev/null +++ b/tests/specs/npm/npmrc_bad_token/.npmrc @@ -0,0 +1,2 @@ +@denotest:registry=http://127.0.0.1:4261/ +//127.0.0.1:4261/denotest/:_authToken=invalid-token diff --git a/tests/specs/npm/npmrc_bad_token/__test__.jsonc b/tests/specs/npm/npmrc_bad_token/__test__.jsonc new file mode 100644 index 00000000000000..fe99247b1bbe26 --- /dev/null +++ b/tests/specs/npm/npmrc_bad_token/__test__.jsonc @@ -0,0 +1,9 @@ +{ + "envs": { + "DENO_FUTURE": "1" + }, + "tempDir": true, + "args": "install", + "output": "main.out", + "exitCode": 1 +} diff --git a/tests/specs/npm/npmrc_bad_token/main.js b/tests/specs/npm/npmrc_bad_token/main.js new file mode 100644 index 00000000000000..e8ccf7611c787d --- /dev/null +++ b/tests/specs/npm/npmrc_bad_token/main.js @@ -0,0 +1,5 @@ +import { getValue, setValue } from "@denotest/basic"; + +console.log(getValue()); +setValue(42); +console.log(getValue()); diff --git a/tests/specs/npm/npmrc_bad_token/main.out b/tests/specs/npm/npmrc_bad_token/main.out new file mode 100644 index 00000000000000..ceee1fed42bf61 --- /dev/null +++ b/tests/specs/npm/npmrc_bad_token/main.out @@ -0,0 +1,4 @@ +⚠️ `deno install` behavior will change in Deno 2. To preserve the current behavior use the `-g` or `--global` flag. +Download http://127.0.0.1:4261/@denotest/basic +error: Error getting response at http://127.0.0.1:4261/@denotest/basic for package "@denotest/basic": Bad response: 401 +[WILDCARD] \ No newline at end of file diff --git a/tests/specs/npm/npmrc_bad_token/package.json b/tests/specs/npm/npmrc_bad_token/package.json new file mode 100644 index 00000000000000..b5ca7df4216a28 --- /dev/null +++ b/tests/specs/npm/npmrc_bad_token/package.json @@ -0,0 +1,7 @@ +{ + "name": "npmrc_test", + "version": "0.0.1", + "dependencies": { + "@denotest/basic": "1.0.0" + } +} diff --git a/tests/specs/npm/npmrc_basic_auth/.npmrc b/tests/specs/npm/npmrc_basic_auth/.npmrc new file mode 100644 index 00000000000000..0624091f00304a --- /dev/null +++ b/tests/specs/npm/npmrc_basic_auth/.npmrc @@ -0,0 +1,4 @@ +@denotest:registry=http://127.0.0.1:4261/ +//127.0.0.1:4261/denotest/:_authToken=private-reg-token +@denotest2:registry=http://127.0.0.1:4262/ +//127.0.0.1:4262/denotest2/:_authToken=private-reg-token2 diff --git a/tests/specs/npm/npmrc_basic_auth/__test__.jsonc b/tests/specs/npm/npmrc_basic_auth/__test__.jsonc new file mode 100644 index 00000000000000..44298ed2f5e07b --- /dev/null +++ b/tests/specs/npm/npmrc_basic_auth/__test__.jsonc @@ -0,0 +1,13 @@ +{ + "envs": { + "DENO_FUTURE": "1" + }, + "tempDir": true, + "steps": [{ + "args": "install", + "output": "install.out" + }, { + "args": "run -A main.js", + "output": "main.out" + }] +} diff --git a/tests/specs/npm/npmrc_basic_auth/install.out b/tests/specs/npm/npmrc_basic_auth/install.out new file mode 100644 index 00000000000000..7484405db9dd8b --- /dev/null +++ b/tests/specs/npm/npmrc_basic_auth/install.out @@ -0,0 +1,9 @@ +⚠️ `deno install` behavior will change in Deno 2. To preserve the current behavior use the `-g` or `--global` flag. +[UNORDERED_START] +Download http://127.0.0.1:4261/@denotest/basic +Download http://127.0.0.1:4262/@denotest2/basic +Download http://localhost:4261/@denotest/basic/1.0.0.tgz +Download http://localhost:4262/@denotest2/basic/1.0.0.tgz +Initialize @denotest2/basic@1.0.0 +Initialize @denotest/basic@1.0.0 +[UNORDERED_END] diff --git a/tests/specs/npm/npmrc_basic_auth/main.js b/tests/specs/npm/npmrc_basic_auth/main.js new file mode 100644 index 00000000000000..66b39363600109 --- /dev/null +++ b/tests/specs/npm/npmrc_basic_auth/main.js @@ -0,0 +1,8 @@ +import { getValue, setValue } from "@denotest/basic"; +import * as test from "@denotest2/basic"; + +console.log(getValue()); +setValue(42); +console.log(getValue()); + +console.log(test.getValue()); diff --git a/tests/specs/npm/npmrc_basic_auth/main.out b/tests/specs/npm/npmrc_basic_auth/main.out new file mode 100644 index 00000000000000..bbe210bdbc6684 --- /dev/null +++ b/tests/specs/npm/npmrc_basic_auth/main.out @@ -0,0 +1,3 @@ +0 +42 +0 diff --git a/tests/specs/npm/npmrc_basic_auth/package.json b/tests/specs/npm/npmrc_basic_auth/package.json new file mode 100644 index 00000000000000..274d1ed7f437b8 --- /dev/null +++ b/tests/specs/npm/npmrc_basic_auth/package.json @@ -0,0 +1,8 @@ +{ + "name": "npmrc_test", + "version": "0.0.1", + "dependencies": { + "@denotest/basic": "1.0.0", + "@denotest2/basic": "1.0.0" + } +} diff --git a/tests/specs/npm/npmrc_deno_json/.npmrc b/tests/specs/npm/npmrc_deno_json/.npmrc new file mode 100644 index 00000000000000..18887e8d33f54a --- /dev/null +++ b/tests/specs/npm/npmrc_deno_json/.npmrc @@ -0,0 +1,2 @@ +@denotest:registry=http://127.0.0.1:4261/ +//127.0.0.1:4261/denotest/:_authToken=private-reg-token diff --git a/tests/specs/npm/npmrc_deno_json/__test__.jsonc b/tests/specs/npm/npmrc_deno_json/__test__.jsonc new file mode 100644 index 00000000000000..5d07b70342f580 --- /dev/null +++ b/tests/specs/npm/npmrc_deno_json/__test__.jsonc @@ -0,0 +1,8 @@ +{ + "envs": { + "DENO_FUTURE": "1" + }, + "tempDir": true, + "args": "run -A main.js", + "output": "main.out" +} diff --git a/tests/specs/npm/npmrc_deno_json/deno.json b/tests/specs/npm/npmrc_deno_json/deno.json new file mode 100644 index 00000000000000..f6ca8454c56395 --- /dev/null +++ b/tests/specs/npm/npmrc_deno_json/deno.json @@ -0,0 +1,3 @@ +{ + "imports": {} +} diff --git a/tests/specs/npm/npmrc_deno_json/main.js b/tests/specs/npm/npmrc_deno_json/main.js new file mode 100644 index 00000000000000..9246715d1f1006 --- /dev/null +++ b/tests/specs/npm/npmrc_deno_json/main.js @@ -0,0 +1,5 @@ +import { getValue, setValue } from "npm:@denotest/basic"; + +console.log(getValue()); +setValue(42); +console.log(getValue()); diff --git a/tests/specs/npm/npmrc_deno_json/main.out b/tests/specs/npm/npmrc_deno_json/main.out new file mode 100644 index 00000000000000..6a1e47669c5d65 --- /dev/null +++ b/tests/specs/npm/npmrc_deno_json/main.out @@ -0,0 +1,4 @@ +Download http://127.0.0.1:4261/@denotest/basic +Download http://localhost:4261/@denotest/basic/1.0.0.tgz +0 +42 diff --git a/tests/specs/npm/npmrc_not_next_to_package_json/.npmrc b/tests/specs/npm/npmrc_not_next_to_package_json/.npmrc new file mode 100644 index 00000000000000..18887e8d33f54a --- /dev/null +++ b/tests/specs/npm/npmrc_not_next_to_package_json/.npmrc @@ -0,0 +1,2 @@ +@denotest:registry=http://127.0.0.1:4261/ +//127.0.0.1:4261/denotest/:_authToken=private-reg-token diff --git a/tests/specs/npm/npmrc_not_next_to_package_json/__test__.jsonc b/tests/specs/npm/npmrc_not_next_to_package_json/__test__.jsonc new file mode 100644 index 00000000000000..fa545c2902bf3a --- /dev/null +++ b/tests/specs/npm/npmrc_not_next_to_package_json/__test__.jsonc @@ -0,0 +1,9 @@ +{ + "envs": { + "DENO_FUTURE": "1" + }, + "tempDir": true, + "args": "install -A -L debug", + "output": "main.out", + "cwd": "subdir" +} diff --git a/tests/specs/npm/npmrc_not_next_to_package_json/main.out b/tests/specs/npm/npmrc_not_next_to_package_json/main.out new file mode 100644 index 00000000000000..8f42fb6d817958 --- /dev/null +++ b/tests/specs/npm/npmrc_not_next_to_package_json/main.out @@ -0,0 +1,6 @@ +[# This test is still downloading code, because we have a private registry] +[# that serves the same packages. The important bit is the message below.] +[WILDCARD] No .npmrc file found +[WILDCARD] +Download http://localhost:4260/@denotest/esm-basic/1.0.0.tgz +Initialize @denotest/esm-basic@1.0.0 diff --git a/tests/specs/npm/npmrc_not_next_to_package_json/subdir/main.js b/tests/specs/npm/npmrc_not_next_to_package_json/subdir/main.js new file mode 100644 index 00000000000000..81dd521da72565 --- /dev/null +++ b/tests/specs/npm/npmrc_not_next_to_package_json/subdir/main.js @@ -0,0 +1,5 @@ +import { getValue, setValue } from "@denotest/esm-basic"; + +console.log(getValue()); +setValue(42); +console.log(getValue()); diff --git a/tests/specs/npm/npmrc_not_next_to_package_json/subdir/package.json b/tests/specs/npm/npmrc_not_next_to_package_json/subdir/package.json new file mode 100644 index 00000000000000..99feed9d83ae09 --- /dev/null +++ b/tests/specs/npm/npmrc_not_next_to_package_json/subdir/package.json @@ -0,0 +1,7 @@ +{ + "name": "npmrc_test", + "version": "0.0.1", + "dependencies": { + "@denotest/esm-basic": "1.0.0" + } +} diff --git a/tests/util/server/src/lib.rs b/tests/util/server/src/lib.rs index ee5348049d454a..a7f4fb44765336 100644 --- a/tests/util/server/src/lib.rs +++ b/tests/util/server/src/lib.rs @@ -307,7 +307,7 @@ async fn get_tcp_listener_stream( futures::stream::select_all(listeners) } -pub const TEST_SERVERS_COUNT: usize = 28; +pub const TEST_SERVERS_COUNT: usize = 30; #[derive(Default)] struct HttpServerCount { diff --git a/tests/util/server/src/npm.rs b/tests/util/server/src/npm.rs index 7cb6a04f2c52b7..363a45d7e5d8b3 100644 --- a/tests/util/server/src/npm.rs +++ b/tests/util/server/src/npm.rs @@ -30,7 +30,6 @@ pub static PUBLIC_TEST_NPM_REGISTRY: Lazy = Lazy::new(|| { ) }); -// TODO: rewrite to use config pub static PRIVATE_TEST_NPM_REGISTRY_1: Lazy = Lazy::new(|| { TestNpmRegistry::new( @@ -43,6 +42,18 @@ pub static PRIVATE_TEST_NPM_REGISTRY_1: Lazy = ) }); +pub static PRIVATE_TEST_NPM_REGISTRY_2: Lazy = + Lazy::new(|| { + TestNpmRegistry::new( + NpmRegistryKind::Private, + &format!( + "http://localhost:{}", + crate::servers::PRIVATE_NPM_REGISTRY_2_PORT + ), + "npm-private2", + ) + }); + pub enum NpmRegistryKind { Public, Private, diff --git a/tests/util/server/src/servers/mod.rs b/tests/util/server/src/servers/mod.rs index 4c86eb4514d529..1b6e2f71df25bf 100644 --- a/tests/util/server/src/servers/mod.rs +++ b/tests/util/server/src/servers/mod.rs @@ -90,6 +90,7 @@ const JSR_REGISTRY_SERVER_PORT: u16 = 4250; const PROVENANCE_MOCK_SERVER_PORT: u16 = 4251; pub(crate) const PUBLIC_NPM_REGISTRY_PORT: u16 = 4260; pub(crate) const PRIVATE_NPM_REGISTRY_1_PORT: u16 = 4261; +pub(crate) const PRIVATE_NPM_REGISTRY_2_PORT: u16 = 4262; // Use the single-threaded scheduler. The hyper server is used as a point of // comparison for the (single-threaded!) benchmarks in cli/bench. We're not @@ -140,6 +141,8 @@ pub async fn run_all_servers() { npm_registry::public_npm_registry(PUBLIC_NPM_REGISTRY_PORT); let private_npm_registry_1_server_futs = npm_registry::private_npm_registry1(PRIVATE_NPM_REGISTRY_1_PORT); + let private_npm_registry_2_server_futs = + npm_registry::private_npm_registry2(PRIVATE_NPM_REGISTRY_2_PORT); let mut futures = vec![ redirect_server_fut.boxed_local(), @@ -169,6 +172,7 @@ pub async fn run_all_servers() { ]; futures.extend(npm_registry_server_futs); futures.extend(private_npm_registry_1_server_futs); + futures.extend(private_npm_registry_2_server_futs); assert_eq!(futures.len(), TEST_SERVERS_COUNT); diff --git a/tests/util/server/src/servers/npm_registry.rs b/tests/util/server/src/servers/npm_registry.rs index eadfafe02e5dd9..7e5547b5bab090 100644 --- a/tests/util/server/src/servers/npm_registry.rs +++ b/tests/util/server/src/servers/npm_registry.rs @@ -33,6 +33,12 @@ pub fn public_npm_registry(port: u16) -> Vec> { } const PRIVATE_NPM_REGISTRY_AUTH_TOKEN: &str = "private-reg-token"; +const PRIVATE_NPM_REGISTRY_2_AUTH_TOKEN: &str = "private-reg-token2"; + +// `deno:land` encoded using base64 +const PRIVATE_NPM_REGISTRY_AUTH_BASE64: &str = "ZGVubzpsYW5k"; +// `deno:land2` encoded using base64 +const PRIVATE_NPM_REGISTRY_2_AUTH_BASE64: &str = "ZGVubzpsYW5kMg=="; pub fn private_npm_registry1(port: u16) -> Vec> { run_npm_server( @@ -42,6 +48,14 @@ pub fn private_npm_registry1(port: u16) -> Vec> { ) } +pub fn private_npm_registry2(port: u16) -> Vec> { + run_npm_server( + port, + "npm private registry server error", + private_npm_registry2_handler, + ) +} + fn run_npm_server( port: u16, error_msg: &'static str, @@ -91,7 +105,9 @@ async fn private_npm_registry1_handler( .get("authorization") .and_then(|x| x.to_str().ok()) .unwrap_or_default(); - if auth != format!("Bearer {}", PRIVATE_NPM_REGISTRY_AUTH_TOKEN) { + if auth != format!("Bearer {}", PRIVATE_NPM_REGISTRY_AUTH_TOKEN) + && auth != format!("Basic {}", PRIVATE_NPM_REGISTRY_AUTH_BASE64) + { return Ok( Response::builder() .status(StatusCode::UNAUTHORIZED) @@ -103,6 +119,28 @@ async fn private_npm_registry1_handler( handle_req_for_registry(req, &npm::PRIVATE_TEST_NPM_REGISTRY_1).await } +async fn private_npm_registry2_handler( + req: Request, +) -> Result>, anyhow::Error> { + let auth = req + .headers() + .get("authorization") + .and_then(|x| x.to_str().ok()) + .unwrap_or_default(); + if auth != format!("Bearer {}", PRIVATE_NPM_REGISTRY_2_AUTH_TOKEN) + && auth != format!("Basic {}", PRIVATE_NPM_REGISTRY_2_AUTH_BASE64) + { + return Ok( + Response::builder() + .status(StatusCode::UNAUTHORIZED) + .body(empty_body()) + .unwrap(), + ); + } + + handle_req_for_registry(req, &npm::PRIVATE_TEST_NPM_REGISTRY_2).await +} + async fn handle_req_for_registry( req: Request, test_npm_registry: &npm::TestNpmRegistry,