summaryrefslogtreecommitdiff
path: root/cli/npm/registry.rs
diff options
context:
space:
mode:
Diffstat (limited to 'cli/npm/registry.rs')
-rw-r--r--cli/npm/registry.rs256
1 files changed, 164 insertions, 92 deletions
diff --git a/cli/npm/registry.rs b/cli/npm/registry.rs
index a758ae7be..510f58132 100644
--- a/cli/npm/registry.rs
+++ b/cli/npm/registry.rs
@@ -9,19 +9,19 @@ use std::io::ErrorKind;
use std::path::PathBuf;
use std::sync::Arc;
+use async_trait::async_trait;
use deno_core::anyhow::bail;
use deno_core::anyhow::Context;
use deno_core::error::custom_error;
use deno_core::error::AnyError;
-use deno_core::futures::future::BoxFuture;
-use deno_core::futures::FutureExt;
+use deno_core::futures;
use deno_core::parking_lot::Mutex;
use deno_core::serde::Deserialize;
use deno_core::serde_json;
use deno_core::url::Url;
-use deno_graph::semver::Version;
+use deno_graph::npm::NpmPackageNv;
use deno_graph::semver::VersionReq;
-use deno_runtime::colors;
+use once_cell::sync::Lazy;
use serde::Serialize;
use crate::args::package_json::parse_dep_entry_name_and_raw_version;
@@ -31,6 +31,7 @@ use crate::http_util::HttpClient;
use crate::util::fs::atomic_write_file;
use crate::util::progress_bar::ProgressBar;
+use super::cache::should_sync_download;
use super::cache::NpmCache;
// npm registry docs: https://github.com/npm/registry/blob/master/docs/REGISTRY-API.md
@@ -43,7 +44,7 @@ pub struct NpmPackageInfo {
pub dist_tags: HashMap<String, String>,
}
-#[derive(Debug, Eq, PartialEq)]
+#[derive(Debug, Clone, Eq, PartialEq)]
pub enum NpmDependencyEntryKind {
Dep,
Peer,
@@ -56,7 +57,7 @@ impl NpmDependencyEntryKind {
}
}
-#[derive(Debug, Eq, PartialEq)]
+#[derive(Debug, Clone, Eq, PartialEq)]
pub struct NpmDependencyEntry {
pub kind: NpmDependencyEntryKind,
pub bare_specifier: String,
@@ -181,83 +182,30 @@ impl NpmPackageVersionDistInfo {
}
}
-pub trait NpmRegistryApi: Clone + Sync + Send + 'static {
- fn maybe_package_info(
- &self,
- name: &str,
- ) -> BoxFuture<'static, Result<Option<Arc<NpmPackageInfo>>, AnyError>>;
-
- fn package_info(
- &self,
- name: &str,
- ) -> BoxFuture<'static, Result<Arc<NpmPackageInfo>, AnyError>> {
- let api = self.clone();
- let name = name.to_string();
- async move {
- let maybe_package_info = api.maybe_package_info(&name).await?;
- match maybe_package_info {
- Some(package_info) => Ok(package_info),
- None => bail!("npm package '{}' does not exist", name),
+static NPM_REGISTRY_DEFAULT_URL: Lazy<Url> = Lazy::new(|| {
+ let env_var_name = "NPM_CONFIG_REGISTRY";
+ if let Ok(registry_url) = std::env::var(env_var_name) {
+ // ensure there is a trailing slash for the directory
+ let registry_url = format!("{}/", registry_url.trim_end_matches('/'));
+ match Url::parse(&registry_url) {
+ Ok(url) => {
+ return url;
+ }
+ Err(err) => {
+ log::debug!("Invalid {} environment variable: {:#}", env_var_name, err,);
}
}
- .boxed()
- }
-
- fn package_version_info(
- &self,
- name: &str,
- version: &Version,
- ) -> BoxFuture<'static, Result<Option<NpmPackageVersionInfo>, AnyError>> {
- let api = self.clone();
- let name = name.to_string();
- let version = version.to_string();
- async move {
- let package_info = api.package_info(&name).await?;
- Ok(package_info.versions.get(&version).cloned())
- }
- .boxed()
}
- /// Clears the internal memory cache.
- fn clear_memory_cache(&self);
-}
+ Url::parse("https://registry.npmjs.org").unwrap()
+});
-#[derive(Clone)]
-pub struct RealNpmRegistryApi(Arc<RealNpmRegistryApiInner>);
-
-impl RealNpmRegistryApi {
- pub fn default_url() -> Url {
- // todo(dsherret): remove DENO_NPM_REGISTRY in the future (maybe May 2023)
- let env_var_names = ["NPM_CONFIG_REGISTRY", "DENO_NPM_REGISTRY"];
- for env_var_name in env_var_names {
- if let Ok(registry_url) = std::env::var(env_var_name) {
- // ensure there is a trailing slash for the directory
- let registry_url = format!("{}/", registry_url.trim_end_matches('/'));
- match Url::parse(&registry_url) {
- Ok(url) => {
- if env_var_name == "DENO_NPM_REGISTRY" {
- log::warn!(
- "{}",
- colors::yellow(concat!(
- "DENO_NPM_REGISTRY was intended for internal testing purposes only. ",
- "Please update to NPM_CONFIG_REGISTRY instead.",
- )),
- );
- }
- return url;
- }
- Err(err) => {
- log::debug!(
- "Invalid {} environment variable: {:#}",
- env_var_name,
- err,
- );
- }
- }
- }
- }
+#[derive(Clone, Debug)]
+pub struct NpmRegistryApi(Arc<dyn NpmRegistryApiInner>);
- Url::parse("https://registry.npmjs.org").unwrap()
+impl NpmRegistryApi {
+ pub fn default_url() -> &'static Url {
+ &NPM_REGISTRY_DEFAULT_URL
}
pub fn new(
@@ -276,26 +224,110 @@ impl RealNpmRegistryApi {
}))
}
+ #[cfg(test)]
+ pub fn new_for_test(api: TestNpmRegistryApiInner) -> NpmRegistryApi {
+ Self(Arc::new(api))
+ }
+
+ pub async fn package_info(
+ &self,
+ name: &str,
+ ) -> Result<Arc<NpmPackageInfo>, AnyError> {
+ let maybe_package_info = self.0.maybe_package_info(name).await?;
+ match maybe_package_info {
+ Some(package_info) => Ok(package_info),
+ None => bail!("npm package '{}' does not exist", name),
+ }
+ }
+
+ pub async fn package_version_info(
+ &self,
+ nv: &NpmPackageNv,
+ ) -> Result<Option<NpmPackageVersionInfo>, AnyError> {
+ let package_info = self.package_info(&nv.name).await?;
+ Ok(package_info.versions.get(&nv.version.to_string()).cloned())
+ }
+
+ /// Caches all the package information in memory in parallel.
+ pub async fn cache_in_parallel(
+ &self,
+ package_names: Vec<String>,
+ ) -> Result<(), AnyError> {
+ let mut unresolved_tasks = Vec::with_capacity(package_names.len());
+
+ // cache the package info up front in parallel
+ if should_sync_download() {
+ // for deterministic test output
+ let mut ordered_names = package_names;
+ ordered_names.sort();
+ for name in ordered_names {
+ self.package_info(&name).await?;
+ }
+ } else {
+ for name in package_names {
+ let api = self.clone();
+ unresolved_tasks.push(tokio::task::spawn(async move {
+ // This is ok to call because api will internally cache
+ // the package information in memory.
+ api.package_info(&name).await
+ }));
+ }
+ };
+
+ for result in futures::future::join_all(unresolved_tasks).await {
+ result??; // surface the first error
+ }
+
+ Ok(())
+ }
+
+ /// Clears the internal memory cache.
+ pub fn clear_memory_cache(&self) {
+ self.0.clear_memory_cache();
+ }
+
pub fn base_url(&self) -> &Url {
- &self.0.base_url
+ self.0.base_url()
}
}
-impl NpmRegistryApi for RealNpmRegistryApi {
- fn maybe_package_info(
+#[async_trait]
+trait NpmRegistryApiInner: std::fmt::Debug + Sync + Send + 'static {
+ async fn maybe_package_info(
&self,
name: &str,
- ) -> BoxFuture<'static, Result<Option<Arc<NpmPackageInfo>>, AnyError>> {
- let api = self.clone();
- let name = name.to_string();
- async move { api.0.maybe_package_info(&name).await }.boxed()
+ ) -> Result<Option<Arc<NpmPackageInfo>>, AnyError>;
+
+ fn clear_memory_cache(&self);
+
+ fn get_cached_package_info(&self, name: &str) -> Option<Arc<NpmPackageInfo>>;
+
+ fn base_url(&self) -> &Url;
+}
+
+#[async_trait]
+impl NpmRegistryApiInner for RealNpmRegistryApiInner {
+ fn base_url(&self) -> &Url {
+ &self.base_url
+ }
+
+ async fn maybe_package_info(
+ &self,
+ name: &str,
+ ) -> Result<Option<Arc<NpmPackageInfo>>, AnyError> {
+ self.maybe_package_info(name).await
}
fn clear_memory_cache(&self) {
- self.0.mem_cache.lock().clear();
+ self.mem_cache.lock().clear();
+ }
+
+ fn get_cached_package_info(&self, name: &str) -> Option<Arc<NpmPackageInfo>> {
+ self.mem_cache.lock().get(name).cloned().flatten()
}
}
+#[derive(Debug)]
struct RealNpmRegistryApiInner {
base_url: Url,
cache: NpmCache,
@@ -461,16 +493,44 @@ impl RealNpmRegistryApiInner {
}
}
+#[derive(Debug)]
+struct NullNpmRegistryApiInner;
+
+#[async_trait]
+impl NpmRegistryApiInner for NullNpmRegistryApiInner {
+ async fn maybe_package_info(
+ &self,
+ _name: &str,
+ ) -> Result<Option<Arc<NpmPackageInfo>>, AnyError> {
+ Err(deno_core::anyhow::anyhow!(
+ "Deno bug. Please report. Registry API was not initialized."
+ ))
+ }
+
+ fn clear_memory_cache(&self) {}
+
+ fn get_cached_package_info(
+ &self,
+ _name: &str,
+ ) -> Option<Arc<NpmPackageInfo>> {
+ None
+ }
+
+ fn base_url(&self) -> &Url {
+ NpmRegistryApi::default_url()
+ }
+}
+
/// Note: This test struct is not thread safe for setup
/// purposes. Construct everything on the same thread.
#[cfg(test)]
-#[derive(Clone, Default)]
-pub struct TestNpmRegistryApi {
+#[derive(Clone, Default, Debug)]
+pub struct TestNpmRegistryApiInner {
package_infos: Arc<Mutex<HashMap<String, NpmPackageInfo>>>,
}
#[cfg(test)]
-impl TestNpmRegistryApi {
+impl TestNpmRegistryApiInner {
pub fn add_package_info(&self, name: &str, info: NpmPackageInfo) {
let previous = self.package_infos.lock().insert(name.to_string(), info);
assert!(previous.is_none());
@@ -554,16 +614,28 @@ impl TestNpmRegistryApi {
}
#[cfg(test)]
-impl NpmRegistryApi for TestNpmRegistryApi {
- fn maybe_package_info(
+#[async_trait]
+impl NpmRegistryApiInner for TestNpmRegistryApiInner {
+ async fn maybe_package_info(
&self,
name: &str,
- ) -> BoxFuture<'static, Result<Option<Arc<NpmPackageInfo>>, AnyError>> {
+ ) -> Result<Option<Arc<NpmPackageInfo>>, AnyError> {
let result = self.package_infos.lock().get(name).cloned();
- Box::pin(deno_core::futures::future::ready(Ok(result.map(Arc::new))))
+ Ok(result.map(Arc::new))
}
fn clear_memory_cache(&self) {
// do nothing for the test api
}
+
+ fn get_cached_package_info(
+ &self,
+ _name: &str,
+ ) -> Option<Arc<NpmPackageInfo>> {
+ None
+ }
+
+ fn base_url(&self) -> &Url {
+ NpmRegistryApi::default_url()
+ }
}