summaryrefslogtreecommitdiff
path: root/cli/npm
diff options
context:
space:
mode:
Diffstat (limited to 'cli/npm')
-rw-r--r--cli/npm/cache.rs37
-rw-r--r--cli/npm/installer.rs28
-rw-r--r--cli/npm/mod.rs9
-rw-r--r--cli/npm/registry.rs613
-rw-r--r--cli/npm/resolution.rs312
-rw-r--r--cli/npm/resolution/common.rs243
-rw-r--r--cli/npm/resolution/graph.rs3726
-rw-r--r--cli/npm/resolution/mod.rs636
-rw-r--r--cli/npm/resolution/snapshot.rs492
-rw-r--r--cli/npm/resolvers/common.rs4
-rw-r--r--cli/npm/resolvers/global.rs6
-rw-r--r--cli/npm/resolvers/local.rs17
-rw-r--r--cli/npm/resolvers/mod.rs19
-rw-r--r--cli/npm/tarball.rs6
14 files changed, 481 insertions, 5667 deletions
diff --git a/cli/npm/cache.rs b/cli/npm/cache.rs
index 81fb76772..3c37aebe9 100644
--- a/cli/npm/cache.rs
+++ b/cli/npm/cache.rs
@@ -13,8 +13,10 @@ use deno_core::error::custom_error;
use deno_core::error::AnyError;
use deno_core::parking_lot::Mutex;
use deno_core::url::Url;
-use deno_graph::npm::NpmPackageNv;
-use deno_graph::semver::Version;
+use deno_npm::registry::NpmPackageVersionDistInfo;
+use deno_npm::NpmPackageCacheFolderId;
+use deno_semver::npm::NpmPackageNv;
+use deno_semver::Version;
use once_cell::sync::Lazy;
use crate::args::CacheSetting;
@@ -25,7 +27,6 @@ use crate::util::fs::hard_link_dir_recursive;
use crate::util::path::root_url_to_safe_local_dirname;
use crate::util::progress_bar::ProgressBar;
-use super::registry::NpmPackageVersionDistInfo;
use super::tarball::verify_and_extract_tarball;
static SHOULD_SYNC_DOWNLOAD: Lazy<bool> =
@@ -112,32 +113,6 @@ pub fn with_folder_sync_lock(
}
}
-pub struct NpmPackageCacheFolderId {
- pub nv: NpmPackageNv,
- /// Peer dependency resolution may require us to have duplicate copies
- /// of the same package.
- pub copy_index: usize,
-}
-
-impl NpmPackageCacheFolderId {
- pub fn with_no_count(&self) -> Self {
- Self {
- nv: self.nv.clone(),
- copy_index: 0,
- }
- }
-}
-
-impl std::fmt::Display for NpmPackageCacheFolderId {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- write!(f, "{}", self.nv)?;
- if self.copy_index > 0 {
- write!(f, "_{}", self.copy_index)?;
- }
- Ok(())
- }
-}
-
#[derive(Clone, Debug)]
pub struct ReadonlyNpmCache {
root_dir: PathBuf,
@@ -515,8 +490,8 @@ pub fn mixed_case_package_name_decode(name: &str) -> Option<String> {
#[cfg(test)]
mod test {
use deno_core::url::Url;
- use deno_graph::npm::NpmPackageNv;
- use deno_graph::semver::Version;
+ use deno_semver::npm::NpmPackageNv;
+ use deno_semver::Version;
use super::ReadonlyNpmCache;
use crate::npm::cache::NpmPackageCacheFolderId;
diff --git a/cli/npm/installer.rs b/cli/npm/installer.rs
index 72a58fb53..6d048f7ca 100644
--- a/cli/npm/installer.rs
+++ b/cli/npm/installer.rs
@@ -4,16 +4,19 @@ use std::sync::atomic::AtomicBool;
use std::sync::Arc;
use deno_core::error::AnyError;
+use deno_core::futures::stream::FuturesOrdered;
+use deno_core::futures::StreamExt;
+use deno_npm::registry::NpmRegistryApi;
use crate::args::package_json::PackageJsonDeps;
-use super::NpmRegistryApi;
+use super::NpmRegistry;
use super::NpmResolution;
#[derive(Debug)]
struct PackageJsonDepsInstallerInner {
has_installed: AtomicBool,
- npm_registry_api: NpmRegistryApi,
+ npm_registry_api: NpmRegistry,
npm_resolution: NpmResolution,
package_deps: PackageJsonDeps,
}
@@ -24,7 +27,7 @@ pub struct PackageJsonDepsInstaller(Option<Arc<PackageJsonDepsInstallerInner>>);
impl PackageJsonDepsInstaller {
pub fn new(
- npm_registry_api: NpmRegistryApi,
+ npm_registry_api: NpmRegistry,
npm_resolution: NpmResolution,
deps: Option<PackageJsonDeps>,
) -> Self {
@@ -76,17 +79,20 @@ impl PackageJsonDepsInstaller {
.collect::<Vec<_>>();
package_reqs.sort(); // deterministic resolution
- inner
- .npm_registry_api
- .cache_in_parallel(
- package_reqs.iter().map(|req| req.name.clone()).collect(),
- )
- .await?;
+ let mut req_with_infos =
+ FuturesOrdered::from_iter(package_reqs.into_iter().map(|req| {
+ let api = inner.npm_registry_api.clone();
+ async move {
+ let info = api.package_info(&req.name).await?;
+ Ok::<_, AnyError>((req, info))
+ }
+ }));
- for package_req in package_reqs {
+ while let Some(result) = req_with_infos.next().await {
+ let (req, info) = result?;
inner
.npm_resolution
- .resolve_package_req_as_pending(package_req)?;
+ .resolve_package_req_as_pending_with_info(req, &info)?;
}
Ok(())
diff --git a/cli/npm/mod.rs b/cli/npm/mod.rs
index b1ce6fda4..95a0a3017 100644
--- a/cli/npm/mod.rs
+++ b/cli/npm/mod.rs
@@ -10,15 +10,8 @@ mod tarball;
pub use cache::should_sync_download;
pub use cache::NpmCache;
pub use installer::PackageJsonDepsInstaller;
-#[cfg(test)]
-pub use registry::NpmPackageVersionDistInfo;
-pub use registry::NpmRegistryApi;
-#[cfg(test)]
-pub use registry::TestNpmRegistryApiInner;
-pub use resolution::NpmPackageId;
+pub use registry::NpmRegistry;
pub use resolution::NpmResolution;
-pub use resolution::NpmResolutionPackage;
-pub use resolution::NpmResolutionSnapshot;
pub use resolvers::create_npm_fs_resolver;
pub use resolvers::NpmPackageResolver;
pub use resolvers::NpmProcessState;
diff --git a/cli/npm/registry.rs b/cli/npm/registry.rs
index 75760c171..0dcdb720a 100644
--- a/cli/npm/registry.rs
+++ b/cli/npm/registry.rs
@@ -1,7 +1,5 @@
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
-use std::borrow::Cow;
-use std::cmp::Ordering;
use std::collections::HashMap;
use std::collections::HashSet;
use std::fs;
@@ -10,21 +8,21 @@ use std::path::PathBuf;
use std::sync::Arc;
use async_trait::async_trait;
-use deno_core::anyhow::bail;
+use deno_core::anyhow::anyhow;
use deno_core::anyhow::Context;
use deno_core::error::custom_error;
use deno_core::error::AnyError;
-use deno_core::futures;
+use deno_core::futures::future::BoxFuture;
+use deno_core::futures::future::Shared;
+use deno_core::futures::FutureExt;
use deno_core::parking_lot::Mutex;
-use deno_core::serde::Deserialize;
use deno_core::serde_json;
use deno_core::url::Url;
-use deno_graph::npm::NpmPackageNv;
-use deno_graph::semver::VersionReq;
+use deno_core::TaskQueue;
+use deno_npm::registry::NpmPackageInfo;
+use deno_npm::registry::NpmRegistryApi;
use once_cell::sync::Lazy;
-use serde::Serialize;
-use crate::args::package_json::parse_dep_entry_name_and_raw_version;
use crate::args::CacheSetting;
use crate::cache::CACHE_PERM;
use crate::http_util::HttpClient;
@@ -34,162 +32,6 @@ 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
-
-#[derive(Debug, Default, Deserialize, Serialize, Clone)]
-pub struct NpmPackageInfo {
- pub name: String,
- pub versions: HashMap<String, NpmPackageVersionInfo>,
- #[serde(rename = "dist-tags")]
- pub dist_tags: HashMap<String, String>,
-}
-
-#[derive(Debug, Clone, Eq, PartialEq)]
-pub enum NpmDependencyEntryKind {
- Dep,
- Peer,
- OptionalPeer,
-}
-
-impl NpmDependencyEntryKind {
- pub fn is_optional(&self) -> bool {
- matches!(self, NpmDependencyEntryKind::OptionalPeer)
- }
-}
-
-#[derive(Debug, Clone, Eq, PartialEq)]
-pub struct NpmDependencyEntry {
- pub kind: NpmDependencyEntryKind,
- pub bare_specifier: String,
- pub name: String,
- pub version_req: VersionReq,
- /// When the dependency is also marked as a peer dependency,
- /// use this entry to resolve the dependency when it can't
- /// be resolved as a peer dependency.
- pub peer_dep_version_req: Option<VersionReq>,
-}
-
-impl PartialOrd for NpmDependencyEntry {
- fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
- Some(self.cmp(other))
- }
-}
-
-impl Ord for NpmDependencyEntry {
- fn cmp(&self, other: &Self) -> std::cmp::Ordering {
- // sort the dependencies alphabetically by name then by version descending
- match self.name.cmp(&other.name) {
- // sort by newest to oldest
- Ordering::Equal => other
- .version_req
- .version_text()
- .cmp(self.version_req.version_text()),
- ordering => ordering,
- }
- }
-}
-
-#[derive(Debug, Default, Deserialize, Serialize, Clone, PartialEq, Eq)]
-pub struct NpmPeerDependencyMeta {
- #[serde(default)]
- optional: bool,
-}
-
-#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)]
-#[serde(untagged)]
-pub enum NpmPackageVersionBinEntry {
- String(String),
- Map(HashMap<String, String>),
-}
-
-#[derive(Debug, Default, Deserialize, Serialize, Clone, PartialEq, Eq)]
-#[serde(rename_all = "camelCase")]
-pub struct NpmPackageVersionInfo {
- pub version: String,
- pub dist: NpmPackageVersionDistInfo,
- pub bin: Option<NpmPackageVersionBinEntry>,
- // Bare specifier to version (ex. `"typescript": "^3.0.1") or possibly
- // package and version (ex. `"typescript-3.0.1": "npm:typescript@3.0.1"`).
- #[serde(default)]
- pub dependencies: HashMap<String, String>,
- #[serde(default)]
- pub peer_dependencies: HashMap<String, String>,
- #[serde(default)]
- pub peer_dependencies_meta: HashMap<String, NpmPeerDependencyMeta>,
-}
-
-impl NpmPackageVersionInfo {
- pub fn dependencies_as_entries(
- &self,
- ) -> Result<Vec<NpmDependencyEntry>, AnyError> {
- fn parse_dep_entry(
- (key, value): (&String, &String),
- kind: NpmDependencyEntryKind,
- ) -> Result<NpmDependencyEntry, AnyError> {
- let (name, version_req) =
- parse_dep_entry_name_and_raw_version(key, value)?;
- let version_req =
- VersionReq::parse_from_npm(version_req).with_context(|| {
- format!("error parsing version requirement for dependency: {key}@{version_req}")
- })?;
- Ok(NpmDependencyEntry {
- kind,
- bare_specifier: key.to_string(),
- name: name.to_string(),
- version_req,
- peer_dep_version_req: None,
- })
- }
-
- let mut result = HashMap::with_capacity(
- self.dependencies.len() + self.peer_dependencies.len(),
- );
- for entry in &self.peer_dependencies {
- let is_optional = self
- .peer_dependencies_meta
- .get(entry.0)
- .map(|d| d.optional)
- .unwrap_or(false);
- let kind = match is_optional {
- true => NpmDependencyEntryKind::OptionalPeer,
- false => NpmDependencyEntryKind::Peer,
- };
- let entry = parse_dep_entry(entry, kind)?;
- result.insert(entry.bare_specifier.clone(), entry);
- }
- for entry in &self.dependencies {
- let entry = parse_dep_entry(entry, NpmDependencyEntryKind::Dep)?;
- // people may define a dependency as a peer dependency as well,
- // so in those cases, attempt to resolve as a peer dependency,
- // but then use this dependency version requirement otherwise
- if let Some(peer_dep_entry) = result.get_mut(&entry.bare_specifier) {
- peer_dep_entry.peer_dep_version_req = Some(entry.version_req);
- } else {
- result.insert(entry.bare_specifier.clone(), entry);
- }
- }
- Ok(result.into_values().collect())
- }
-}
-
-#[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq, Eq)]
-pub struct NpmPackageVersionDistInfo {
- /// URL to the tarball.
- pub tarball: String,
- shasum: String,
- integrity: Option<String>,
-}
-
-impl NpmPackageVersionDistInfo {
- pub fn integrity(&self) -> Cow<String> {
- self
- .integrity
- .as_ref()
- .map(Cow::Borrowed)
- .unwrap_or_else(|| Cow::Owned(format!("sha1-{}", self.shasum)))
- }
-}
-
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) {
@@ -209,9 +51,9 @@ static NPM_REGISTRY_DEFAULT_URL: Lazy<Url> = Lazy::new(|| {
});
#[derive(Clone, Debug)]
-pub struct NpmRegistryApi(Arc<dyn NpmRegistryApiInner>);
+pub struct NpmRegistry(Option<Arc<NpmRegistryApiInner>>);
-impl NpmRegistryApi {
+impl NpmRegistry {
pub fn default_url() -> &'static Url {
&NPM_REGISTRY_DEFAULT_URL
}
@@ -222,188 +64,142 @@ impl NpmRegistryApi {
http_client: HttpClient,
progress_bar: ProgressBar,
) -> Self {
- Self(Arc::new(RealNpmRegistryApiInner {
+ Self(Some(Arc::new(NpmRegistryApiInner {
base_url,
cache,
mem_cache: Default::default(),
previously_reloaded_packages: Default::default(),
http_client,
progress_bar,
- }))
+ })))
}
- /// Creates an npm registry API that will be uninitialized
- /// and error for every request. This is useful for tests
- /// or for initializing the LSP.
+ /// Creates an npm registry API that will be uninitialized. This is
+ /// useful for tests or for initializing the LSP.
pub fn new_uninitialized() -> Self {
- Self(Arc::new(NullNpmRegistryApiInner))
- }
-
- #[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(())
+ Self(None)
}
/// Clears the internal memory cache.
pub fn clear_memory_cache(&self) {
- self.0.clear_memory_cache();
+ self.inner().clear_memory_cache();
}
pub fn get_cached_package_info(
&self,
name: &str,
) -> Option<Arc<NpmPackageInfo>> {
- self.0.get_cached_package_info(name)
+ self.inner().get_cached_package_info(name)
}
pub fn base_url(&self) -> &Url {
- self.0.base_url()
+ &self.inner().base_url
}
-}
-
-#[async_trait]
-trait NpmRegistryApiInner: std::fmt::Debug + Sync + Send + 'static {
- async fn maybe_package_info(
- &self,
- name: &str,
- ) -> 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;
+ fn inner(&self) -> &Arc<NpmRegistryApiInner> {
+ // this panicking indicates a bug in the code where this
+ // wasn't initialized
+ self.0.as_ref().unwrap()
+ }
}
-#[async_trait]
-impl NpmRegistryApiInner for RealNpmRegistryApiInner {
- fn base_url(&self) -> &Url {
- &self.base_url
- }
+static SYNC_DOWNLOAD_TASK_QUEUE: Lazy<TaskQueue> =
+ Lazy::new(TaskQueue::default);
+#[async_trait]
+impl NpmRegistryApi for NpmRegistry {
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.mem_cache.lock().clear();
+ if should_sync_download() {
+ let inner = self.inner().clone();
+ SYNC_DOWNLOAD_TASK_QUEUE
+ .queue(async move { inner.maybe_package_info(name).await })
+ .await
+ } else {
+ self.inner().maybe_package_info(name).await
+ }
}
+}
- fn get_cached_package_info(&self, name: &str) -> Option<Arc<NpmPackageInfo>> {
- self.mem_cache.lock().get(name).cloned().flatten()
- }
+#[derive(Debug)]
+enum CacheItem {
+ Pending(
+ Shared<BoxFuture<'static, Result<Option<Arc<NpmPackageInfo>>, String>>>,
+ ),
+ Resolved(Option<Arc<NpmPackageInfo>>),
}
#[derive(Debug)]
-struct RealNpmRegistryApiInner {
+struct NpmRegistryApiInner {
base_url: Url,
cache: NpmCache,
- mem_cache: Mutex<HashMap<String, Option<Arc<NpmPackageInfo>>>>,
+ mem_cache: Mutex<HashMap<String, CacheItem>>,
previously_reloaded_packages: Mutex<HashSet<String>>,
http_client: HttpClient,
progress_bar: ProgressBar,
}
-impl RealNpmRegistryApiInner {
+impl NpmRegistryApiInner {
pub async fn maybe_package_info(
- &self,
+ self: &Arc<Self>,
name: &str,
) -> Result<Option<Arc<NpmPackageInfo>>, AnyError> {
- let maybe_maybe_info = self.mem_cache.lock().get(name).cloned();
- if let Some(maybe_info) = maybe_maybe_info {
- Ok(maybe_info)
- } else {
- let mut maybe_package_info = None;
- if self.cache.cache_setting().should_use_for_npm_package(name)
+ let (created, future) = {
+ let mut mem_cache = self.mem_cache.lock();
+ match mem_cache.get(name) {
+ Some(CacheItem::Resolved(maybe_info)) => {
+ return Ok(maybe_info.clone());
+ }
+ Some(CacheItem::Pending(future)) => (false, future.clone()),
+ None => {
+ if self.cache.cache_setting().should_use_for_npm_package(name)
// if this has been previously reloaded, then try loading from the
// file system cache
|| !self.previously_reloaded_packages.lock().insert(name.to_string())
- {
- // attempt to load from the file cache
- maybe_package_info = self.load_file_cached_package_info(name);
- }
-
- if maybe_package_info.is_none() {
- maybe_package_info = self
- .load_package_info_from_registry(name)
- .await
- .with_context(|| {
- format!(
- "Error getting response at {} for package \"{}\"",
- self.get_package_url(name),
- name
- )
- })?;
+ {
+ // attempt to load from the file cache
+ if let Some(info) = self.load_file_cached_package_info(name) {
+ let result = Some(Arc::new(info));
+ mem_cache
+ .insert(name.to_string(), CacheItem::Resolved(result.clone()));
+ return Ok(result);
+ }
+ }
+
+ let future = {
+ let api = self.clone();
+ let name = name.to_string();
+ async move { api.load_package_info_from_registry(&name).await }
+ .boxed()
+ .shared()
+ };
+ mem_cache
+ .insert(name.to_string(), CacheItem::Pending(future.clone()));
+ (true, future)
+ }
}
- let maybe_package_info = maybe_package_info.map(Arc::new);
+ };
- // Not worth the complexity to ensure multiple in-flight requests
- // for the same package only request once because with how this is
- // used that should never happen.
- let mut mem_cache = self.mem_cache.lock();
- Ok(match mem_cache.get(name) {
- // another thread raced here, so use its result instead
- Some(info) => info.clone(),
- None => {
- mem_cache.insert(name.to_string(), maybe_package_info.clone());
- maybe_package_info
+ if created {
+ match future.await {
+ Ok(maybe_info) => {
+ // replace the cache item to say it's resolved now
+ self
+ .mem_cache
+ .lock()
+ .insert(name.to_string(), CacheItem::Resolved(maybe_info.clone()));
+ Ok(maybe_info)
}
- })
+ Err(err) => {
+ // purge the item from the cache so it loads next time
+ self.mem_cache.lock().remove(name);
+ Err(anyhow!("{}", err))
+ }
+ }
+ } else {
+ Ok(future.await.map_err(|err| anyhow!("{}", err))?)
}
}
@@ -478,6 +274,25 @@ impl RealNpmRegistryApiInner {
async fn load_package_info_from_registry(
&self,
name: &str,
+ ) -> Result<Option<Arc<NpmPackageInfo>>, String> {
+ self
+ .load_package_info_from_registry_inner(name)
+ .await
+ .with_context(|| {
+ format!(
+ "Error getting response at {} for package \"{}\"",
+ self.get_package_url(name),
+ name
+ )
+ })
+ .map(|info| info.map(Arc::new))
+ // make cloneable
+ .map_err(|err| format!("{err:#}"))
+ }
+
+ async fn load_package_info_from_registry_inner(
+ &self,
+ name: &str,
) -> Result<Option<NpmPackageInfo>, AnyError> {
if *self.cache.cache_setting() == CacheSetting::Only {
return Err(custom_error(
@@ -513,206 +328,20 @@ impl RealNpmRegistryApiInner {
let name_folder_path = self.cache.package_name_folder(name, &self.base_url);
name_folder_path.join("registry.json")
}
-}
-
-#[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, Debug)]
-pub struct TestNpmRegistryApiInner {
- package_infos: Arc<Mutex<HashMap<String, NpmPackageInfo>>>,
-}
-
-#[cfg(test)]
-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());
- }
-
- pub fn ensure_package(&self, name: &str) {
- if !self.package_infos.lock().contains_key(name) {
- self.add_package_info(
- name,
- NpmPackageInfo {
- name: name.to_string(),
- ..Default::default()
- },
- );
- }
- }
- pub fn ensure_package_version(&self, name: &str, version: &str) {
- self.ensure_package(name);
- let mut infos = self.package_infos.lock();
- let info = infos.get_mut(name).unwrap();
- if !info.versions.contains_key(version) {
- info.versions.insert(
- version.to_string(),
- NpmPackageVersionInfo {
- version: version.to_string(),
- ..Default::default()
- },
- );
- }
- }
-
- pub fn add_dependency(
- &self,
- package_from: (&str, &str),
- package_to: (&str, &str),
- ) {
- let mut infos = self.package_infos.lock();
- let info = infos.get_mut(package_from.0).unwrap();
- let version = info.versions.get_mut(package_from.1).unwrap();
- version
- .dependencies
- .insert(package_to.0.to_string(), package_to.1.to_string());
- }
-
- pub fn add_dist_tag(&self, package_name: &str, tag: &str, version: &str) {
- let mut infos = self.package_infos.lock();
- let info = infos.get_mut(package_name).unwrap();
- info.dist_tags.insert(tag.to_string(), version.to_string());
- }
-
- pub fn add_peer_dependency(
- &self,
- package_from: (&str, &str),
- package_to: (&str, &str),
- ) {
- let mut infos = self.package_infos.lock();
- let info = infos.get_mut(package_from.0).unwrap();
- let version = info.versions.get_mut(package_from.1).unwrap();
- version
- .peer_dependencies
- .insert(package_to.0.to_string(), package_to.1.to_string());
- }
-
- pub fn add_optional_peer_dependency(
- &self,
- package_from: (&str, &str),
- package_to: (&str, &str),
- ) {
- let mut infos = self.package_infos.lock();
- let info = infos.get_mut(package_from.0).unwrap();
- let version = info.versions.get_mut(package_from.1).unwrap();
- version
- .peer_dependencies
- .insert(package_to.0.to_string(), package_to.1.to_string());
- version.peer_dependencies_meta.insert(
- package_to.0.to_string(),
- NpmPeerDependencyMeta { optional: true },
- );
+ pub fn clear_memory_cache(&self) {
+ self.mem_cache.lock().clear();
}
-}
-#[cfg(test)]
-#[async_trait]
-impl NpmRegistryApiInner for TestNpmRegistryApiInner {
- async fn maybe_package_info(
+ pub fn get_cached_package_info(
&self,
name: &str,
- ) -> Result<Option<Arc<NpmPackageInfo>>, AnyError> {
- let result = self.package_infos.lock().get(name).cloned();
- 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()
- }
-}
-
-#[cfg(test)]
-mod test {
- use std::collections::HashMap;
-
- use deno_core::serde_json;
-
- use crate::npm::registry::NpmPackageVersionBinEntry;
- use crate::npm::NpmPackageVersionDistInfo;
-
- use super::NpmPackageVersionInfo;
-
- #[test]
- fn deserializes_minimal_pkg_info() {
- let text = r#"{ "version": "1.0.0", "dist": { "tarball": "value", "shasum": "test" } }"#;
- let info: NpmPackageVersionInfo = serde_json::from_str(text).unwrap();
- assert_eq!(
- info,
- NpmPackageVersionInfo {
- version: "1.0.0".to_string(),
- dist: NpmPackageVersionDistInfo {
- tarball: "value".to_string(),
- shasum: "test".to_string(),
- integrity: None,
- },
- bin: None,
- dependencies: Default::default(),
- peer_dependencies: Default::default(),
- peer_dependencies_meta: Default::default()
- }
- );
- }
-
- #[test]
- fn deserializes_bin_entry() {
- // string
- let text = r#"{ "version": "1.0.0", "bin": "bin-value", "dist": { "tarball": "value", "shasum": "test" } }"#;
- let info: NpmPackageVersionInfo = serde_json::from_str(text).unwrap();
- assert_eq!(
- info.bin,
- Some(NpmPackageVersionBinEntry::String("bin-value".to_string()))
- );
-
- // map
- let text = r#"{ "version": "1.0.0", "bin": { "a": "a-value", "b": "b-value" }, "dist": { "tarball": "value", "shasum": "test" } }"#;
- let info: NpmPackageVersionInfo = serde_json::from_str(text).unwrap();
- assert_eq!(
- info.bin,
- Some(NpmPackageVersionBinEntry::Map(HashMap::from([
- ("a".to_string(), "a-value".to_string()),
- ("b".to_string(), "b-value".to_string()),
- ])))
- );
+ let mem_cache = self.mem_cache.lock();
+ if let Some(CacheItem::Resolved(maybe_info)) = mem_cache.get(name) {
+ maybe_info.clone()
+ } else {
+ None
+ }
}
}
diff --git a/cli/npm/resolution.rs b/cli/npm/resolution.rs
new file mode 100644
index 000000000..291acf4bc
--- /dev/null
+++ b/cli/npm/resolution.rs
@@ -0,0 +1,312 @@
+// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+
+use std::collections::HashSet;
+use std::sync::Arc;
+
+use deno_core::error::AnyError;
+use deno_core::parking_lot::Mutex;
+use deno_core::parking_lot::RwLock;
+use deno_core::TaskQueue;
+use deno_lockfile::NpmPackageDependencyLockfileInfo;
+use deno_lockfile::NpmPackageLockfileInfo;
+use deno_npm::registry::NpmPackageInfo;
+use deno_npm::resolution::NpmPackagesPartitioned;
+use deno_npm::resolution::NpmResolutionSnapshot;
+use deno_npm::NpmPackageCacheFolderId;
+use deno_npm::NpmPackageId;
+use deno_npm::NpmResolutionPackage;
+use deno_semver::npm::NpmPackageNv;
+use deno_semver::npm::NpmPackageNvReference;
+use deno_semver::npm::NpmPackageReq;
+use deno_semver::npm::NpmPackageReqReference;
+
+use crate::args::Lockfile;
+
+use super::registry::NpmRegistry;
+
+/// 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.
+///
+/// This does not interact with the file system.
+#[derive(Clone)]
+pub struct NpmResolution(Arc<NpmResolutionInner>);
+
+struct NpmResolutionInner {
+ api: NpmRegistry,
+ snapshot: RwLock<NpmResolutionSnapshot>,
+ update_queue: TaskQueue,
+ maybe_lockfile: Option<Arc<Mutex<Lockfile>>>,
+}
+
+impl std::fmt::Debug for NpmResolution {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ let snapshot = self.0.snapshot.read();
+ f.debug_struct("NpmResolution")
+ .field("snapshot", &snapshot)
+ .finish()
+ }
+}
+
+impl NpmResolution {
+ pub fn new(
+ api: NpmRegistry,
+ initial_snapshot: Option<NpmResolutionSnapshot>,
+ maybe_lockfile: Option<Arc<Mutex<Lockfile>>>,
+ ) -> Self {
+ Self(Arc::new(NpmResolutionInner {
+ api,
+ snapshot: RwLock::new(initial_snapshot.unwrap_or_default()),
+ update_queue: Default::default(),
+ maybe_lockfile,
+ }))
+ }
+
+ pub async fn add_package_reqs(
+ &self,
+ package_reqs: Vec<NpmPackageReq>,
+ ) -> Result<(), AnyError> {
+ let inner = &self.0;
+
+ // only allow one thread in here at a time
+ let _permit = inner.update_queue.acquire().await;
+ let snapshot = inner.snapshot.read().clone();
+
+ let snapshot = add_package_reqs_to_snapshot(
+ &inner.api,
+ package_reqs,
+ snapshot,
+ self.0.maybe_lockfile.clone(),
+ )
+ .await?;
+
+ *inner.snapshot.write() = snapshot;
+ Ok(())
+ }
+
+ pub async fn set_package_reqs(
+ &self,
+ package_reqs: Vec<NpmPackageReq>,
+ ) -> Result<(), AnyError> {
+ let inner = &self.0;
+ // only allow one thread in here at a time
+ let _permit = inner.update_queue.acquire().await;
+ let snapshot = inner.snapshot.read().clone();
+
+ let reqs_set = package_reqs.iter().collect::<HashSet<_>>();
+ let has_removed_package = !snapshot
+ .package_reqs()
+ .keys()
+ .all(|req| reqs_set.contains(req));
+ // if any packages were removed, we need to completely recreate the npm resolution snapshot
+ let snapshot = if has_removed_package {
+ NpmResolutionSnapshot::default()
+ } else {
+ snapshot
+ };
+ let snapshot = add_package_reqs_to_snapshot(
+ &inner.api,
+ package_reqs,
+ snapshot,
+ self.0.maybe_lockfile.clone(),
+ )
+ .await?;
+
+ *inner.snapshot.write() = snapshot;
+
+ Ok(())
+ }
+
+ pub async fn resolve_pending(&self) -> Result<(), AnyError> {
+ let inner = &self.0;
+ // only allow one thread in here at a time
+ let _permit = inner.update_queue.acquire().await;
+ let snapshot = inner.snapshot.read().clone();
+
+ let snapshot = add_package_reqs_to_snapshot(
+ &inner.api,
+ Vec::new(),
+ snapshot,
+ self.0.maybe_lockfile.clone(),
+ )
+ .await?;
+
+ *inner.snapshot.write() = snapshot;
+
+ Ok(())
+ }
+
+ pub fn pkg_req_ref_to_nv_ref(
+ &self,
+ req_ref: NpmPackageReqReference,
+ ) -> Result<NpmPackageNvReference, AnyError> {
+ let node_id = self.resolve_pkg_id_from_pkg_req(&req_ref.req)?;
+ Ok(NpmPackageNvReference {
+ nv: node_id.nv,
+ sub_path: req_ref.sub_path,
+ })
+ }
+
+ pub fn resolve_package_cache_folder_id_from_id(
+ &self,
+ id: &NpmPackageId,
+ ) -> Option<NpmPackageCacheFolderId> {
+ self
+ .0
+ .snapshot
+ .read()
+ .package_from_id(id)
+ .map(|p| p.get_package_cache_folder_id())
+ }
+
+ pub fn resolve_package_from_package(
+ &self,
+ name: &str,
+ referrer: &NpmPackageCacheFolderId,
+ ) -> Result<NpmResolutionPackage, AnyError> {
+ self
+ .0
+ .snapshot
+ .read()
+ .resolve_package_from_package(name, referrer)
+ .cloned()
+ }
+
+ /// Resolve a node package from a deno module.
+ pub fn resolve_pkg_id_from_pkg_req(
+ &self,
+ req: &NpmPackageReq,
+ ) -> Result<NpmPackageId, AnyError> {
+ self
+ .0
+ .snapshot
+ .read()
+ .resolve_pkg_from_pkg_req(req)
+ .map(|pkg| pkg.pkg_id.clone())
+ }
+
+ pub fn resolve_pkg_id_from_deno_module(
+ &self,
+ id: &NpmPackageNv,
+ ) -> Result<NpmPackageId, AnyError> {
+ self
+ .0
+ .snapshot
+ .read()
+ .resolve_package_from_deno_module(id)
+ .map(|pkg| pkg.pkg_id.clone())
+ }
+
+ /// Resolves a package requirement for deno graph. This should only be
+ /// called by deno_graph's NpmResolver or for resolving packages in
+ /// a package.json
+ pub fn resolve_package_req_as_pending(
+ &self,
+ pkg_req: &NpmPackageReq,
+ ) -> Result<NpmPackageNv, AnyError> {
+ // we should always have this because it should have been cached before here
+ let package_info =
+ self.0.api.get_cached_package_info(&pkg_req.name).unwrap();
+ self.resolve_package_req_as_pending_with_info(pkg_req, &package_info)
+ }
+
+ /// Resolves a package requirement for deno graph. This should only be
+ /// called by deno_graph's NpmResolver or for resolving packages in
+ /// a package.json
+ pub fn resolve_package_req_as_pending_with_info(
+ &self,
+ pkg_req: &NpmPackageReq,
+ package_info: &NpmPackageInfo,
+ ) -> Result<NpmPackageNv, AnyError> {
+ debug_assert_eq!(pkg_req.name, package_info.name);
+ let inner = &self.0;
+ let mut snapshot = inner.snapshot.write();
+ let nv = snapshot.resolve_package_req_as_pending(pkg_req, package_info)?;
+ Ok(nv)
+ }
+
+ pub fn all_packages_partitioned(&self) -> NpmPackagesPartitioned {
+ self.0.snapshot.read().all_packages_partitioned()
+ }
+
+ pub fn has_packages(&self) -> bool {
+ !self.0.snapshot.read().is_empty()
+ }
+
+ pub fn snapshot(&self) -> NpmResolutionSnapshot {
+ self.0.snapshot.read().clone()
+ }
+
+ pub fn lock(&self, lockfile: &mut Lockfile) -> Result<(), AnyError> {
+ let snapshot = self.0.snapshot.read();
+ populate_lockfile_from_snapshot(lockfile, &snapshot)
+ }
+}
+
+async fn add_package_reqs_to_snapshot(
+ api: &NpmRegistry,
+ package_reqs: Vec<NpmPackageReq>,
+ snapshot: NpmResolutionSnapshot,
+ maybe_lockfile: Option<Arc<Mutex<Lockfile>>>,
+) -> Result<NpmResolutionSnapshot, AnyError> {
+ if !snapshot.has_pending()
+ && package_reqs
+ .iter()
+ .all(|req| snapshot.package_reqs().contains_key(req))
+ {
+ return Ok(snapshot); // already up to date
+ }
+
+ let result = snapshot.resolve_pending(package_reqs, api).await;
+ api.clear_memory_cache();
+ let snapshot = result?; // propagate the error after clearing the memory cache
+
+ if let Some(lockfile_mutex) = maybe_lockfile {
+ let mut lockfile = lockfile_mutex.lock();
+ populate_lockfile_from_snapshot(&mut lockfile, &snapshot)?;
+ Ok(snapshot)
+ } else {
+ Ok(snapshot)
+ }
+}
+
+fn populate_lockfile_from_snapshot(
+ lockfile: &mut Lockfile,
+ snapshot: &NpmResolutionSnapshot,
+) -> Result<(), AnyError> {
+ for (package_req, nv) in snapshot.package_reqs() {
+ lockfile.insert_npm_specifier(
+ package_req.to_string(),
+ snapshot
+ .resolve_package_from_deno_module(nv)
+ .unwrap()
+ .pkg_id
+ .as_serialized(),
+ );
+ }
+ for package in snapshot.all_packages() {
+ lockfile
+ .check_or_insert_npm_package(npm_package_to_lockfile_info(package))?;
+ }
+ Ok(())
+}
+
+fn npm_package_to_lockfile_info(
+ pkg: NpmResolutionPackage,
+) -> NpmPackageLockfileInfo {
+ let dependencies = pkg
+ .dependencies
+ .into_iter()
+ .map(|(name, id)| NpmPackageDependencyLockfileInfo {
+ name,
+ id: id.as_serialized(),
+ })
+ .collect();
+
+ NpmPackageLockfileInfo {
+ display_id: pkg.pkg_id.nv.to_string(),
+ serialized_id: pkg.pkg_id.as_serialized(),
+ integrity: pkg.dist.integrity().to_string(),
+ dependencies,
+ }
+}
diff --git a/cli/npm/resolution/common.rs b/cli/npm/resolution/common.rs
deleted file mode 100644
index eb3e51f7d..000000000
--- a/cli/npm/resolution/common.rs
+++ /dev/null
@@ -1,243 +0,0 @@
-// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
-
-use deno_core::anyhow::bail;
-use deno_core::error::AnyError;
-use deno_graph::semver::Version;
-use deno_graph::semver::VersionReq;
-use once_cell::sync::Lazy;
-
-use super::NpmPackageId;
-use crate::npm::registry::NpmPackageInfo;
-use crate::npm::registry::NpmPackageVersionInfo;
-
-pub static LATEST_VERSION_REQ: Lazy<VersionReq> =
- Lazy::new(|| VersionReq::parse_from_specifier("latest").unwrap());
-
-pub fn resolve_best_package_version_and_info<'info, 'version>(
- version_req: &VersionReq,
- package_info: &'info NpmPackageInfo,
- existing_versions: impl Iterator<Item = &'version Version>,
-) -> Result<VersionAndInfo<'info>, AnyError> {
- if let Some(version) = resolve_best_from_existing_versions(
- version_req,
- package_info,
- existing_versions,
- )? {
- match package_info.versions.get(&version.to_string()) {
- Some(version_info) => Ok(VersionAndInfo {
- version,
- info: version_info,
- }),
- None => {
- bail!(
- "could not find version '{}' for '{}'",
- version,
- &package_info.name
- )
- }
- }
- } else {
- // get the information
- get_resolved_package_version_and_info(version_req, package_info, None)
- }
-}
-
-#[derive(Clone)]
-pub struct VersionAndInfo<'a> {
- pub version: Version,
- pub info: &'a NpmPackageVersionInfo,
-}
-
-fn get_resolved_package_version_and_info<'a>(
- version_req: &VersionReq,
- info: &'a NpmPackageInfo,
- parent: Option<&NpmPackageId>,
-) -> Result<VersionAndInfo<'a>, AnyError> {
- if let Some(tag) = version_req.tag() {
- tag_to_version_info(info, tag, parent)
- } else {
- let mut maybe_best_version: Option<VersionAndInfo> = None;
- for version_info in info.versions.values() {
- let version = Version::parse_from_npm(&version_info.version)?;
- if version_req.matches(&version) {
- let is_best_version = maybe_best_version
- .as_ref()
- .map(|best_version| best_version.version.cmp(&version).is_lt())
- .unwrap_or(true);
- if is_best_version {
- maybe_best_version = Some(VersionAndInfo {
- version,
- info: version_info,
- });
- }
- }
- }
-
- match maybe_best_version {
- Some(v) => Ok(v),
- // If the package isn't found, it likely means that the user needs to use
- // `--reload` to get the latest npm package information. Although it seems
- // like we could make this smart by fetching the latest information for
- // this package here, we really need a full restart. There could be very
- // interesting bugs that occur if this package's version was resolved by
- // something previous using the old information, then now being smart here
- // causes a new fetch of the package information, meaning this time the
- // previous resolution of this package's version resolved to an older
- // version, but next time to a different version because it has new information.
- None => bail!(
- concat!(
- "Could not find npm package '{}' matching {}{}. ",
- "Try retrieving the latest npm package information by running with --reload",
- ),
- info.name,
- version_req.version_text(),
- match parent {
- Some(resolved_id) => format!(" as specified in {}", resolved_id.nv),
- None => String::new(),
- }
- ),
- }
- }
-}
-
-pub fn version_req_satisfies(
- version_req: &VersionReq,
- version: &Version,
- package_info: &NpmPackageInfo,
- parent: Option<&NpmPackageId>,
-) -> Result<bool, AnyError> {
- match version_req.tag() {
- Some(tag) => {
- let tag_version = tag_to_version_info(package_info, tag, parent)?.version;
- Ok(tag_version == *version)
- }
- None => Ok(version_req.matches(version)),
- }
-}
-
-fn resolve_best_from_existing_versions<'a>(
- version_req: &VersionReq,
- package_info: &NpmPackageInfo,
- existing_versions: impl Iterator<Item = &'a Version>,
-) -> Result<Option<Version>, AnyError> {
- let mut maybe_best_version: Option<&Version> = None;
- for version in existing_versions {
- if version_req_satisfies(version_req, version, package_info, None)? {
- let is_best_version = maybe_best_version
- .as_ref()
- .map(|best_version| (*best_version).cmp(version).is_lt())
- .unwrap_or(true);
- if is_best_version {
- maybe_best_version = Some(version);
- }
- }
- }
- Ok(maybe_best_version.cloned())
-}
-
-fn tag_to_version_info<'a>(
- info: &'a NpmPackageInfo,
- tag: &str,
- parent: Option<&NpmPackageId>,
-) -> Result<VersionAndInfo<'a>, AnyError> {
- // For when someone just specifies @types/node, we want to pull in a
- // "known good" version of @types/node that works well with Deno and
- // not necessarily the latest version. For example, we might only be
- // compatible with Node vX, but then Node vY is published so we wouldn't
- // want to pull that in.
- // Note: If the user doesn't want this behavior, then they can specify an
- // explicit version.
- if tag == "latest" && info.name == "@types/node" {
- return get_resolved_package_version_and_info(
- // WARNING: When bumping this version, check if anything needs to be
- // updated in the `setNodeOnlyGlobalNames` call in 99_main_compiler.js
- &VersionReq::parse_from_npm("18.0.0 - 18.11.18").unwrap(),
- info,
- parent,
- );
- }
-
- if let Some(version) = info.dist_tags.get(tag) {
- match info.versions.get(version) {
- Some(info) => Ok(VersionAndInfo {
- version: Version::parse_from_npm(version)?,
- info,
- }),
- None => {
- bail!(
- "Could not find version '{}' referenced in dist-tag '{}'.",
- version,
- tag,
- )
- }
- }
- } else {
- bail!("Could not find dist-tag '{}'.", tag)
- }
-}
-
-#[cfg(test)]
-mod test {
- use std::collections::HashMap;
-
- use deno_graph::npm::NpmPackageReqReference;
-
- use super::*;
-
- #[test]
- fn test_get_resolved_package_version_and_info() {
- // dist tag where version doesn't exist
- let package_ref = NpmPackageReqReference::from_str("npm:test").unwrap();
- let package_info = NpmPackageInfo {
- name: "test".to_string(),
- versions: HashMap::new(),
- dist_tags: HashMap::from([(
- "latest".to_string(),
- "1.0.0-alpha".to_string(),
- )]),
- };
- let result = get_resolved_package_version_and_info(
- package_ref
- .req
- .version_req
- .as_ref()
- .unwrap_or(&*LATEST_VERSION_REQ),
- &package_info,
- None,
- );
- assert_eq!(
- result.err().unwrap().to_string(),
- "Could not find version '1.0.0-alpha' referenced in dist-tag 'latest'."
- );
-
- // dist tag where version is a pre-release
- let package_ref = NpmPackageReqReference::from_str("npm:test").unwrap();
- let package_info = NpmPackageInfo {
- name: "test".to_string(),
- versions: HashMap::from([
- ("0.1.0".to_string(), NpmPackageVersionInfo::default()),
- (
- "1.0.0-alpha".to_string(),
- NpmPackageVersionInfo {
- version: "0.1.0-alpha".to_string(),
- ..Default::default()
- },
- ),
- ]),
- dist_tags: HashMap::from([(
- "latest".to_string(),
- "1.0.0-alpha".to_string(),
- )]),
- };
- let result = get_resolved_package_version_and_info(
- package_ref
- .req
- .version_req
- .as_ref()
- .unwrap_or(&*LATEST_VERSION_REQ),
- &package_info,
- None,
- );
- assert_eq!(result.unwrap().version.to_string(), "1.0.0-alpha");
- }
-}
diff --git a/cli/npm/resolution/graph.rs b/cli/npm/resolution/graph.rs
deleted file mode 100644
index 65754f3bf..000000000
--- a/cli/npm/resolution/graph.rs
+++ /dev/null
@@ -1,3726 +0,0 @@
-// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
-
-use std::collections::hash_map::DefaultHasher;
-use std::collections::BTreeMap;
-use std::collections::HashMap;
-use std::collections::HashSet;
-use std::collections::VecDeque;
-use std::hash::Hash;
-use std::hash::Hasher;
-use std::sync::Arc;
-
-use deno_core::anyhow::bail;
-use deno_core::anyhow::Context;
-use deno_core::error::AnyError;
-use deno_core::parking_lot::Mutex;
-use deno_graph::npm::NpmPackageNv;
-use deno_graph::npm::NpmPackageReq;
-use deno_graph::semver::Version;
-use deno_graph::semver::VersionReq;
-use log::debug;
-
-use crate::npm::registry::NpmDependencyEntry;
-use crate::npm::registry::NpmDependencyEntryKind;
-use crate::npm::registry::NpmPackageInfo;
-use crate::npm::registry::NpmPackageVersionInfo;
-use crate::npm::resolution::common::resolve_best_package_version_and_info;
-use crate::npm::resolution::snapshot::SnapshotPackageCopyIndexResolver;
-use crate::npm::NpmRegistryApi;
-
-use super::common::version_req_satisfies;
-use super::common::LATEST_VERSION_REQ;
-use super::snapshot::NpmResolutionSnapshot;
-use super::NpmPackageId;
-use super::NpmResolutionPackage;
-
-// todo(dsherret): for perf we should use an arena/bump allocator for
-// creating the nodes and paths since this is done in a phase
-
-/// A unique identifier to a node in the graph.
-#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)]
-struct NodeId(u32);
-
-/// A resolved package in the resolution graph.
-#[derive(Debug)]
-struct Node {
- /// The specifier to child relationship in the graph. The specifier is
- /// the key in an npm package's dependencies map (ex. "express"). We
- /// use a BTreeMap for some determinism when creating the snapshot.
- ///
- /// Note: We don't want to store the children as a `NodeRef` because
- /// multiple paths might visit through the children and we don't want
- /// to share those references with those paths.
- pub children: BTreeMap<String, NodeId>,
- /// Whether the node has demonstrated to have no peer dependencies in its
- /// descendants. If this is true then we can skip analyzing this node
- /// again when we encounter it another time in the dependency tree, which
- /// is much faster.
- pub no_peers: bool,
-}
-
-#[derive(Clone)]
-enum ResolvedIdPeerDep {
- /// This is a reference to the parent instead of the child because we only have a
- /// node reference to the parent, since we've traversed it, but the child node may
- /// change from under it.
- ParentReference {
- parent: GraphPathNodeOrRoot,
- child_pkg_nv: Arc<NpmPackageNv>,
- },
- /// A node that was created during snapshotting and is not being used in any path.
- SnapshotNodeId(NodeId),
-}
-
-impl ResolvedIdPeerDep {
- pub fn current_state_hash(&self) -> u64 {
- let mut hasher = DefaultHasher::new();
- self.current_state_hash_with_hasher(&mut hasher);
- hasher.finish()
- }
-
- pub fn current_state_hash_with_hasher(&self, hasher: &mut DefaultHasher) {
- match self {
- ResolvedIdPeerDep::ParentReference {
- parent,
- child_pkg_nv,
- } => {
- match parent {
- GraphPathNodeOrRoot::Root(root) => root.hash(hasher),
- GraphPathNodeOrRoot::Node(node) => node.node_id().hash(hasher),
- }
- child_pkg_nv.hash(hasher);
- }
- ResolvedIdPeerDep::SnapshotNodeId(node_id) => {
- node_id.hash(hasher);
- }
- }
- }
-}
-
-/// A pending resolved identifier used in the graph. At the end of resolution, these
-/// will become fully resolved to an `NpmPackageId`.
-#[derive(Clone)]
-struct ResolvedId {
- nv: Arc<NpmPackageNv>,
- peer_dependencies: Vec<ResolvedIdPeerDep>,
-}
-
-impl ResolvedId {
- /// Gets a hash of the resolved identifier at this current moment in time.
- ///
- /// WARNING: A resolved identifier references a value that could change in
- /// the future, so this should be used with that in mind.
- pub fn current_state_hash(&self) -> u64 {
- let mut hasher = DefaultHasher::new();
- self.nv.hash(&mut hasher);
- for dep in &self.peer_dependencies {
- dep.current_state_hash_with_hasher(&mut hasher);
- }
- hasher.finish()
- }
-
- pub fn push_peer_dep(&mut self, peer_dep: ResolvedIdPeerDep) -> bool {
- let new_hash = peer_dep.current_state_hash();
- for dep in &self.peer_dependencies {
- if new_hash == dep.current_state_hash() {
- return false; // peer dep already set
- }
- }
- self.peer_dependencies.push(peer_dep);
- true
- }
-}
-
-/// Mappings of node identifiers to resolved identifiers. Each node has exactly
-/// one resolved identifier.
-///
-/// The mapping from resolved to node_ids is imprecise and will do a best attempt
-/// at sharing nodes.
-#[derive(Default)]
-struct ResolvedNodeIds {
- node_to_resolved_id: HashMap<NodeId, (ResolvedId, u64)>,
- resolved_to_node_id: HashMap<u64, NodeId>,
-}
-
-impl ResolvedNodeIds {
- pub fn set(&mut self, node_id: NodeId, resolved_id: ResolvedId) {
- let resolved_id_hash = resolved_id.current_state_hash();
- if let Some((_, old_resolved_id_key)) = self
- .node_to_resolved_id
- .insert(node_id, (resolved_id, resolved_id_hash))
- {
- // ensure the old resolved id key is removed as it might be stale
- self.resolved_to_node_id.remove(&old_resolved_id_key);
- }
- self.resolved_to_node_id.insert(resolved_id_hash, node_id);
- }
-
- pub fn get(&self, node_id: NodeId) -> Option<&ResolvedId> {
- self.node_to_resolved_id.get(&node_id).map(|(id, _)| id)
- }
-
- pub fn get_node_id(&self, resolved_id: &ResolvedId) -> Option<NodeId> {
- self
- .resolved_to_node_id
- .get(&resolved_id.current_state_hash())
- .copied()
- }
-}
-
-// todo(dsherret): for some reason the lsp errors when using an Rc<RefCell<NodeId>> here
-// instead of an Arc<Mutex<NodeId>>. We should investigate and fix.
-
-/// A pointer to a specific node in a graph path. The underlying node id
-/// may change as peer dependencies are created.
-#[derive(Clone, Debug)]
-struct NodeIdRef(Arc<Mutex<NodeId>>);
-
-impl NodeIdRef {
- pub fn new(node_id: NodeId) -> Self {
- NodeIdRef(Arc::new(Mutex::new(node_id)))
- }
-
- pub fn change(&self, node_id: NodeId) {
- *self.0.lock() = node_id;
- }
-
- pub fn get(&self) -> NodeId {
- *self.0.lock()
- }
-}
-
-#[derive(Clone)]
-enum GraphPathNodeOrRoot {
- Node(Arc<GraphPath>),
- Root(Arc<NpmPackageNv>),
-}
-
-/// Path through the graph that represents a traversal through the graph doing
-/// the dependency resolution. The graph tries to share duplicate package
-/// information and we try to avoid traversing parts of the graph that we know
-/// are resolved.
-struct GraphPath {
- previous_node: Option<GraphPathNodeOrRoot>,
- node_id_ref: NodeIdRef,
- specifier: String,
- // we could consider not storing this here and instead reference the resolved
- // nodes, but we should performance profile this code first
- nv: Arc<NpmPackageNv>,
- /// Descendants in the path that circularly link to an ancestor in a child.These
- /// descendants should be kept up to date and always point to this node.
- linked_circular_descendants: Mutex<Vec<Arc<GraphPath>>>,
-}
-
-impl GraphPath {
- pub fn for_root(node_id: NodeId, nv: Arc<NpmPackageNv>) -> Arc<Self> {
- Arc::new(Self {
- previous_node: Some(GraphPathNodeOrRoot::Root(nv.clone())),
- node_id_ref: NodeIdRef::new(node_id),
- // use an empty specifier
- specifier: "".to_string(),
- nv,
- linked_circular_descendants: Default::default(),
- })
- }
-
- pub fn node_id(&self) -> NodeId {
- self.node_id_ref.get()
- }
-
- pub fn specifier(&self) -> &str {
- &self.specifier
- }
-
- pub fn change_id(&self, node_id: NodeId) {
- self.node_id_ref.change(node_id)
- }
-
- pub fn with_id(
- self: &Arc<GraphPath>,
- node_id: NodeId,
- specifier: String,
- nv: Arc<NpmPackageNv>,
- ) -> Arc<Self> {
- Arc::new(Self {
- previous_node: Some(GraphPathNodeOrRoot::Node(self.clone())),
- node_id_ref: NodeIdRef::new(node_id),
- specifier,
- nv,
- linked_circular_descendants: Default::default(),
- })
- }
-
- /// Gets if there is an ancestor with the same name & version along this path.
- pub fn find_ancestor(&self, nv: &NpmPackageNv) -> Option<Arc<GraphPath>> {
- let mut maybe_next_node = self.previous_node.as_ref();
- while let Some(GraphPathNodeOrRoot::Node(next_node)) = maybe_next_node {
- // we've visited this before, so stop
- if *next_node.nv == *nv {
- return Some(next_node.clone());
- }
- maybe_next_node = next_node.previous_node.as_ref();
- }
- None
- }
-
- /// Gets the bottom-up path to the ancestor not including the current or ancestor node.
- pub fn get_path_to_ancestor_exclusive(
- &self,
- ancestor_node_id: NodeId,
- ) -> Vec<&Arc<GraphPath>> {
- let mut path = Vec::new();
- let mut maybe_next_node = self.previous_node.as_ref();
- while let Some(GraphPathNodeOrRoot::Node(next_node)) = maybe_next_node {
- if next_node.node_id() == ancestor_node_id {
- break;
- }
- path.push(next_node);
- maybe_next_node = next_node.previous_node.as_ref();
- }
- debug_assert!(maybe_next_node.is_some());
- path
- }
-
- pub fn ancestors(&self) -> GraphPathAncestorIterator {
- GraphPathAncestorIterator {
- next: self.previous_node.as_ref(),
- }
- }
-}
-
-struct GraphPathAncestorIterator<'a> {
- next: Option<&'a GraphPathNodeOrRoot>,
-}
-
-impl<'a> Iterator for GraphPathAncestorIterator<'a> {
- type Item = &'a GraphPathNodeOrRoot;
- fn next(&mut self) -> Option<Self::Item> {
- if let Some(next) = self.next.take() {
- if let GraphPathNodeOrRoot::Node(node) = next {
- self.next = node.previous_node.as_ref();
- }
- Some(next)
- } else {
- None
- }
- }
-}
-
-#[derive(Default)]
-pub struct Graph {
- /// Each requirement is mapped to a specific name and version.
- package_reqs: HashMap<NpmPackageReq, Arc<NpmPackageNv>>,
- /// Then each name and version is mapped to an exact node id.
- /// Note: Uses a BTreeMap in order to create some determinism
- /// when creating the snapshot.
- root_packages: BTreeMap<Arc<NpmPackageNv>, NodeId>,
- package_name_versions: HashMap<String, HashSet<Version>>,
- nodes: HashMap<NodeId, Node>,
- resolved_node_ids: ResolvedNodeIds,
- // This will be set when creating from a snapshot, then
- // inform the final snapshot creation.
- packages_to_copy_index: HashMap<NpmPackageId, usize>,
- /// Packages that the resolver should resolve first.
- pending_unresolved_packages: Vec<Arc<NpmPackageNv>>,
-}
-
-impl Graph {
- pub fn from_snapshot(
- snapshot: NpmResolutionSnapshot,
- ) -> Result<Self, AnyError> {
- fn get_or_create_graph_node(
- graph: &mut Graph,
- pkg_id: &NpmPackageId,
- packages: &HashMap<NpmPackageId, NpmResolutionPackage>,
- created_package_ids: &mut HashMap<NpmPackageId, NodeId>,
- ) -> Result<NodeId, AnyError> {
- if let Some(id) = created_package_ids.get(pkg_id) {
- return Ok(*id);
- }
-
- let node_id = graph.create_node(&pkg_id.nv);
- created_package_ids.insert(pkg_id.clone(), node_id);
-
- let peer_dep_ids = pkg_id
- .peer_dependencies
- .iter()
- .map(|peer_dep| {
- Ok(ResolvedIdPeerDep::SnapshotNodeId(get_or_create_graph_node(
- graph,
- peer_dep,
- packages,
- created_package_ids,
- )?))
- })
- .collect::<Result<Vec<_>, AnyError>>()?;
- let graph_resolved_id = ResolvedId {
- nv: Arc::new(pkg_id.nv.clone()),
- peer_dependencies: peer_dep_ids,
- };
- graph.resolved_node_ids.set(node_id, graph_resolved_id);
- let resolution = match packages.get(pkg_id) {
- Some(resolved_id) => resolved_id,
- // maybe the user messed around with the lockfile
- None => bail!("not found package: {}", pkg_id.as_serialized()),
- };
- for (name, child_id) in &resolution.dependencies {
- let child_node_id = get_or_create_graph_node(
- graph,
- child_id,
- packages,
- created_package_ids,
- )?;
- graph.set_child_of_parent_node(node_id, name, child_node_id);
- }
- Ok(node_id)
- }
-
- let mut graph = Self {
- // Note: It might be more correct to store the copy index
- // from past resolutions with the node somehow, but maybe not.
- packages_to_copy_index: snapshot
- .packages
- .iter()
- .map(|(id, p)| (id.clone(), p.copy_index))
- .collect(),
- package_reqs: snapshot
- .package_reqs
- .into_iter()
- .map(|(k, v)| (k, Arc::new(v)))
- .collect(),
- pending_unresolved_packages: snapshot
- .pending_unresolved_packages
- .into_iter()
- .map(Arc::new)
- .collect(),
- ..Default::default()
- };
- let mut created_package_ids =
- HashMap::with_capacity(snapshot.packages.len());
- for (id, resolved_id) in snapshot.root_packages {
- let node_id = get_or_create_graph_node(
- &mut graph,
- &resolved_id,
- &snapshot.packages,
- &mut created_package_ids,
- )?;
- graph.root_packages.insert(Arc::new(id), node_id);
- }
- Ok(graph)
- }
-
- pub fn take_pending_unresolved(&mut self) -> Vec<Arc<NpmPackageNv>> {
- std::mem::take(&mut self.pending_unresolved_packages)
- }
-
- pub fn has_package_req(&self, req: &NpmPackageReq) -> bool {
- self.package_reqs.contains_key(req)
- }
-
- pub fn has_root_package(&self, id: &NpmPackageNv) -> bool {
- self.root_packages.contains_key(id)
- }
-
- fn get_npm_pkg_id(&self, node_id: NodeId) -> NpmPackageId {
- let resolved_id = self.resolved_node_ids.get(node_id).unwrap();
- self.get_npm_pkg_id_from_resolved_id(resolved_id, HashSet::new())
- }
-
- fn get_npm_pkg_id_from_resolved_id(
- &self,
- resolved_id: &ResolvedId,
- seen: HashSet<NodeId>,
- ) -> NpmPackageId {
- if resolved_id.peer_dependencies.is_empty() {
- NpmPackageId {
- nv: (*resolved_id.nv).clone(),
- peer_dependencies: Vec::new(),
- }
- } else {
- let mut npm_pkg_id = NpmPackageId {
- nv: (*resolved_id.nv).clone(),
- peer_dependencies: Vec::with_capacity(
- resolved_id.peer_dependencies.len(),
- ),
- };
- let mut seen_children_resolved_ids =
- HashSet::with_capacity(resolved_id.peer_dependencies.len());
- for peer_dep in &resolved_id.peer_dependencies {
- let maybe_node_and_resolved_id = match peer_dep {
- ResolvedIdPeerDep::SnapshotNodeId(node_id) => self
- .resolved_node_ids
- .get(*node_id)
- .map(|resolved_id| (*node_id, resolved_id)),
- ResolvedIdPeerDep::ParentReference {
- parent,
- child_pkg_nv: child_nv,
- } => match &parent {
- GraphPathNodeOrRoot::Root(_) => {
- self.root_packages.get(child_nv).and_then(|node_id| {
- self
- .resolved_node_ids
- .get(*node_id)
- .map(|resolved_id| (*node_id, resolved_id))
- })
- }
- GraphPathNodeOrRoot::Node(parent_path) => {
- self.nodes.get(&parent_path.node_id()).and_then(|parent| {
- parent
- .children
- .values()
- .filter_map(|child_id| {
- let child_id = *child_id;
- self
- .resolved_node_ids
- .get(child_id)
- .map(|resolved_id| (child_id, resolved_id))
- })
- .find(|(_, resolved_id)| resolved_id.nv == *child_nv)
- })
- }
- },
- };
- // this should always be set
- debug_assert!(maybe_node_and_resolved_id.is_some());
- if let Some((child_id, child_resolved_id)) = maybe_node_and_resolved_id
- {
- let mut new_seen = seen.clone();
- if new_seen.insert(child_id) {
- let child_peer = self.get_npm_pkg_id_from_resolved_id(
- child_resolved_id,
- new_seen.clone(),
- );
-
- if seen_children_resolved_ids.insert(child_peer.clone()) {
- npm_pkg_id.peer_dependencies.push(child_peer);
- }
- }
- }
- }
- npm_pkg_id
- }
- }
-
- fn get_or_create_for_id(
- &mut self,
- resolved_id: &ResolvedId,
- ) -> (bool, NodeId) {
- if let Some(node_id) = self.resolved_node_ids.get_node_id(resolved_id) {
- return (false, node_id);
- }
-
- let node_id = self.create_node(&resolved_id.nv);
- self.resolved_node_ids.set(node_id, resolved_id.clone());
- (true, node_id)
- }
-
- fn create_node(&mut self, pkg_nv: &NpmPackageNv) -> NodeId {
- let node_id = NodeId(self.nodes.len() as u32);
- let node = Node {
- children: Default::default(),
- no_peers: false,
- };
-
- self
- .package_name_versions
- .entry(pkg_nv.name.clone())
- .or_default()
- .insert(pkg_nv.version.clone());
- self.nodes.insert(node_id, node);
-
- node_id
- }
-
- fn borrow_node_mut(&mut self, node_id: NodeId) -> &mut Node {
- self.nodes.get_mut(&node_id).unwrap()
- }
-
- fn set_child_of_parent_node(
- &mut self,
- parent_id: NodeId,
- specifier: &str,
- child_id: NodeId,
- ) {
- assert_ne!(child_id, parent_id);
- let parent = self.borrow_node_mut(parent_id);
- parent.children.insert(specifier.to_string(), child_id);
- }
-
- pub async fn into_snapshot(
- self,
- api: &NpmRegistryApi,
- ) -> Result<NpmResolutionSnapshot, AnyError> {
- let packages_to_pkg_ids = self
- .nodes
- .keys()
- .map(|node_id| (*node_id, self.get_npm_pkg_id(*node_id)))
- .collect::<HashMap<_, _>>();
- let mut copy_index_resolver =
- SnapshotPackageCopyIndexResolver::from_map_with_capacity(
- self.packages_to_copy_index,
- self.nodes.len(),
- );
- let mut packages = HashMap::with_capacity(self.nodes.len());
- let mut packages_by_name: HashMap<String, Vec<_>> =
- HashMap::with_capacity(self.nodes.len());
-
- // todo(dsherret): there is a lurking bug within the peer dependencies code.
- // You can see it by using `NodeIds` instead of `NpmPackageIds` on this travered_ids
- // hashset, which will cause the bottom of the "tree" nodes to be populated in
- // the result instead of the top of the "tree". I think there's maybe one small
- // thing that's not being updated properly.
- let mut traversed_ids = HashSet::with_capacity(self.nodes.len());
- let mut pending = VecDeque::new();
-
- for root_id in self.root_packages.values().copied() {
- let pkg_id = packages_to_pkg_ids.get(&root_id).unwrap();
- if traversed_ids.insert(pkg_id.clone()) {
- pending.push_back((root_id, pkg_id));
- }
- }
-
- while let Some((node_id, pkg_id)) = pending.pop_front() {
- let node = self.nodes.get(&node_id).unwrap();
-
- packages_by_name
- .entry(pkg_id.nv.name.clone())
- .or_default()
- .push(pkg_id.clone());
-
- // todo(dsherret): grab this from the dep entry cache, which should have it
- let dist = api
- .package_version_info(&pkg_id.nv)
- .await?
- .unwrap_or_else(|| panic!("missing: {:?}", pkg_id.nv))
- .dist;
-
- let mut dependencies = HashMap::with_capacity(node.children.len());
- for (specifier, child_id) in &node.children {
- let child_id = *child_id;
- let child_pkg_id = packages_to_pkg_ids.get(&child_id).unwrap();
- if traversed_ids.insert(child_pkg_id.clone()) {
- pending.push_back((child_id, child_pkg_id));
- }
- dependencies.insert(specifier.clone(), (*child_pkg_id).clone());
- }
-
- packages.insert(
- (*pkg_id).clone(),
- NpmResolutionPackage {
- copy_index: copy_index_resolver.resolve(pkg_id),
- pkg_id: (*pkg_id).clone(),
- dist,
- dependencies,
- },
- );
- }
-
- Ok(NpmResolutionSnapshot {
- root_packages: self
- .root_packages
- .into_iter()
- .map(|(nv, node_id)| {
- (
- (*nv).clone(),
- packages_to_pkg_ids.get(&node_id).unwrap().clone(),
- )
- })
- .collect(),
- packages_by_name: packages_by_name
- .into_iter()
- .map(|(name, mut ids)| {
- ids.sort();
- ids.dedup();
- (name, ids)
- })
- .collect(),
- packages,
- package_reqs: self
- .package_reqs
- .into_iter()
- .map(|(req, nv)| (req, (*nv).clone()))
- .collect(),
- pending_unresolved_packages: self
- .pending_unresolved_packages
- .into_iter()
- .map(|nv| (*nv).clone())
- .collect(),
- })
- }
-
- // Debugging methods
-
- #[cfg(debug_assertions)]
- #[allow(unused)]
- fn output_path(&self, path: &Arc<GraphPath>) {
- eprintln!("-----------");
- self.output_node(path.node_id(), false);
- for path in path.ancestors() {
- match path {
- GraphPathNodeOrRoot::Node(node) => {
- self.output_node(node.node_id(), false)
- }
- GraphPathNodeOrRoot::Root(pkg_id) => {
- let node_id = self.root_packages.get(pkg_id).unwrap();
- eprintln!(
- "Root: {} ({}: {})",
- pkg_id,
- node_id.0,
- self.get_npm_pkg_id(*node_id).as_serialized()
- )
- }
- }
- }
- eprintln!("-----------");
- }
-
- #[cfg(debug_assertions)]
- #[allow(unused)]
- fn output_node(&self, node_id: NodeId, show_children: bool) {
- eprintln!(
- "{:>4}: {}",
- node_id.0,
- self.get_npm_pkg_id(node_id).as_serialized()
- );
-
- if show_children {
- let node = self.nodes.get(&node_id).unwrap();
- eprintln!(" Children:");
- for (specifier, child_id) in &node.children {
- eprintln!(" {}: {}", specifier, child_id.0);
- }
- }
- }
-
- #[cfg(debug_assertions)]
- #[allow(unused)]
- pub fn output_nodes(&self) {
- eprintln!("~~~");
- let mut node_ids = self
- .resolved_node_ids
- .node_to_resolved_id
- .keys()
- .copied()
- .collect::<Vec<_>>();
- node_ids.sort_by(|a, b| a.0.cmp(&b.0));
- for node_id in node_ids {
- self.output_node(node_id, true);
- }
- eprintln!("~~~");
- }
-}
-
-#[derive(Default)]
-struct DepEntryCache(HashMap<Arc<NpmPackageNv>, Arc<Vec<NpmDependencyEntry>>>);
-
-impl DepEntryCache {
- pub fn store(
- &mut self,
- nv: Arc<NpmPackageNv>,
- version_info: &NpmPackageVersionInfo,
- ) -> Result<Arc<Vec<NpmDependencyEntry>>, AnyError> {
- debug_assert!(!self.0.contains_key(&nv)); // we should not be re-inserting
- let mut deps = version_info
- .dependencies_as_entries()
- .with_context(|| format!("npm package: {nv}"))?;
- // Ensure name alphabetical and then version descending
- // so these are resolved in that order
- deps.sort();
- let deps = Arc::new(deps);
- self.0.insert(nv, deps.clone());
- Ok(deps)
- }
-
- pub fn get(
- &self,
- id: &NpmPackageNv,
- ) -> Option<&Arc<Vec<NpmDependencyEntry>>> {
- self.0.get(id)
- }
-}
-
-struct UnresolvedOptionalPeer {
- specifier: String,
- graph_path: Arc<GraphPath>,
-}
-
-pub struct GraphDependencyResolver<'a> {
- graph: &'a mut Graph,
- api: &'a NpmRegistryApi,
- pending_unresolved_nodes: VecDeque<Arc<GraphPath>>,
- unresolved_optional_peers:
- HashMap<Arc<NpmPackageNv>, Vec<UnresolvedOptionalPeer>>,
- dep_entry_cache: DepEntryCache,
-}
-
-impl<'a> GraphDependencyResolver<'a> {
- pub fn new(graph: &'a mut Graph, api: &'a NpmRegistryApi) -> Self {
- Self {
- graph,
- api,
- pending_unresolved_nodes: Default::default(),
- unresolved_optional_peers: Default::default(),
- dep_entry_cache: Default::default(),
- }
- }
-
- pub fn add_root_package(
- &mut self,
- package_nv: &NpmPackageNv,
- package_info: &NpmPackageInfo,
- ) -> Result<(), AnyError> {
- if self.graph.root_packages.contains_key(package_nv) {
- return Ok(()); // already added
- }
-
- // todo(dsherret): using a version requirement here is a temporary hack
- // to reuse code in a large refactor. We should resolve the node directly
- // from the package name and version
- let version_req =
- VersionReq::parse_from_specifier(&format!("{}", package_nv.version))
- .unwrap();
- let (pkg_nv, node_id) = self.resolve_node_from_info(
- &package_nv.name,
- &version_req,
- package_info,
- None,
- )?;
- self.graph.root_packages.insert(pkg_nv.clone(), node_id);
- self
- .pending_unresolved_nodes
- .push_back(GraphPath::for_root(node_id, pkg_nv));
- Ok(())
- }
-
- pub fn add_package_req(
- &mut self,
- package_req: &NpmPackageReq,
- package_info: &NpmPackageInfo,
- ) -> Result<(), AnyError> {
- if self.graph.package_reqs.contains_key(package_req) {
- return Ok(()); // already added
- }
-
- let (pkg_id, node_id) = self.resolve_node_from_info(
- &package_req.name,
- package_req
- .version_req
- .as_ref()
- .unwrap_or(&*LATEST_VERSION_REQ),
- package_info,
- None,
- )?;
- self
- .graph
- .package_reqs
- .insert(package_req.clone(), pkg_id.clone());
- self.graph.root_packages.insert(pkg_id.clone(), node_id);
- self
- .pending_unresolved_nodes
- .push_back(GraphPath::for_root(node_id, pkg_id));
- Ok(())
- }
-
- fn analyze_dependency(
- &mut self,
- entry: &NpmDependencyEntry,
- package_info: &NpmPackageInfo,
- parent_path: &Arc<GraphPath>,
- ) -> Result<NodeId, AnyError> {
- debug_assert_eq!(entry.kind, NpmDependencyEntryKind::Dep);
- let parent_id = parent_path.node_id();
- let (child_nv, mut child_id) = self.resolve_node_from_info(
- &entry.name,
- &entry.version_req,
- package_info,
- Some(parent_id),
- )?;
- // Some packages may resolves to themselves as a dependency. If this occurs,
- // just ignore adding these as dependencies because this is likely a mistake
- // in the package.
- if child_id != parent_id {
- let maybe_ancestor = parent_path.find_ancestor(&child_nv);
- if let Some(ancestor) = &maybe_ancestor {
- child_id = ancestor.node_id();
- }
-
- let new_path = parent_path.with_id(
- child_id,
- entry.bare_specifier.to_string(),
- child_nv,
- );
- if let Some(ancestor) = maybe_ancestor {
- // this node is circular, so we link it to the ancestor
- self.add_linked_circular_descendant(&ancestor, new_path);
- } else {
- self.graph.set_child_of_parent_node(
- parent_id,
- &entry.bare_specifier,
- child_id,
- );
- self.pending_unresolved_nodes.push_back(new_path);
- }
- }
- Ok(child_id)
- }
-
- fn resolve_node_from_info(
- &mut self,
- pkg_req_name: &str,
- version_req: &VersionReq,
- package_info: &NpmPackageInfo,
- parent_id: Option<NodeId>,
- ) -> Result<(Arc<NpmPackageNv>, NodeId), AnyError> {
- let version_and_info = resolve_best_package_version_and_info(
- version_req,
- package_info,
- self
- .graph
- .package_name_versions
- .entry(package_info.name.clone())
- .or_default()
- .iter(),
- )?;
- let resolved_id = ResolvedId {
- nv: Arc::new(NpmPackageNv {
- name: package_info.name.to_string(),
- version: version_and_info.version.clone(),
- }),
- peer_dependencies: Vec::new(),
- };
- let (_, node_id) = self.graph.get_or_create_for_id(&resolved_id);
- let pkg_nv = resolved_id.nv;
-
- let has_deps = if let Some(deps) = self.dep_entry_cache.get(&pkg_nv) {
- !deps.is_empty()
- } else {
- let deps = self
- .dep_entry_cache
- .store(pkg_nv.clone(), version_and_info.info)?;
- !deps.is_empty()
- };
-
- if !has_deps {
- // ensure this is set if not, as it's an optimization
- let mut node = self.graph.borrow_node_mut(node_id);
- node.no_peers = true;
- }
-
- debug!(
- "{} - Resolved {}@{} to {}",
- match parent_id {
- Some(parent_id) => self.graph.get_npm_pkg_id(parent_id).as_serialized(),
- None => "<package-req>".to_string(),
- },
- pkg_req_name,
- version_req.version_text(),
- pkg_nv.to_string(),
- );
-
- Ok((pkg_nv, node_id))
- }
-
- pub async fn resolve_pending(&mut self) -> Result<(), AnyError> {
- // go down through the dependencies by tree depth
- while let Some(parent_path) = self.pending_unresolved_nodes.pop_front() {
- let (parent_nv, child_deps) = {
- let node_id = parent_path.node_id();
- if self.graph.nodes.get(&node_id).unwrap().no_peers {
- // We can skip as there's no reason to analyze this graph segment further.
- continue;
- }
-
- let pkg_nv = self
- .graph
- .resolved_node_ids
- .get(node_id)
- .unwrap()
- .nv
- .clone();
- let deps = if let Some(deps) = self.dep_entry_cache.get(&pkg_nv) {
- deps.clone()
- } else {
- // the api should have this in the cache at this point, so no need to parallelize
- match self.api.package_version_info(&pkg_nv).await? {
- Some(version_info) => {
- self.dep_entry_cache.store(pkg_nv.clone(), &version_info)?
- }
- None => {
- bail!("Could not find version information for {}", pkg_nv)
- }
- }
- };
-
- (pkg_nv, deps)
- };
-
- // cache all the dependencies' registry infos in parallel if should
- self
- .api
- .cache_in_parallel({
- child_deps.iter().map(|dep| dep.name.clone()).collect()
- })
- .await?;
-
- // resolve the dependencies
- let mut found_peer = false;
-
- for dep in child_deps.iter() {
- let package_info = self.api.package_info(&dep.name).await?;
-
- match dep.kind {
- NpmDependencyEntryKind::Dep => {
- let parent_id = parent_path.node_id();
- let node = self.graph.nodes.get(&parent_id).unwrap();
- let child_id = match node.children.get(&dep.bare_specifier) {
- Some(child_id) => {
- // this dependency was previously analyzed by another path
- // so we don't attempt to resolve the version again
- let child_id = *child_id;
- let child_nv = self
- .graph
- .resolved_node_ids
- .get(child_id)
- .unwrap()
- .nv
- .clone();
- let maybe_ancestor = parent_path.find_ancestor(&child_nv);
- let child_path = parent_path.with_id(
- child_id,
- dep.bare_specifier.clone(),
- child_nv,
- );
- if let Some(ancestor) = maybe_ancestor {
- // when the nv appears as an ancestor, use that node
- // and mark this as circular
- self.add_linked_circular_descendant(&ancestor, child_path);
- } else {
- // mark the child as pending
- self.pending_unresolved_nodes.push_back(child_path);
- }
- child_id
- }
- None => {
- self.analyze_dependency(dep, &package_info, &parent_path)?
- }
- };
-
- if !found_peer {
- found_peer = !self.graph.borrow_node_mut(child_id).no_peers;
- }
- }
- NpmDependencyEntryKind::Peer
- | NpmDependencyEntryKind::OptionalPeer => {
- found_peer = true;
- // we need to re-evaluate peer dependencies every time and can't
- // skip over them because they might be evaluated differently based
- // on the current path
- let maybe_new_id = self.resolve_peer_dep(
- &dep.bare_specifier,
- dep,
- &package_info,
- &parent_path,
- )?;
-
- // For optional dependencies, we want to resolve them if any future
- // same parent version resolves them. So when not resolved, store them to be
- // potentially resolved later.
- //
- // Note: This is not a good solution, but will probably work ok in most
- // scenarios. We can work on improving this in the future. We probably
- // want to resolve future optional peers to the same dependency for example.
- if dep.kind == NpmDependencyEntryKind::OptionalPeer {
- match maybe_new_id {
- Some(new_id) => {
- if let Some(unresolved_optional_peers) =
- self.unresolved_optional_peers.remove(&parent_nv)
- {
- for optional_peer in unresolved_optional_peers {
- let peer_parent = GraphPathNodeOrRoot::Node(
- optional_peer.graph_path.clone(),
- );
- self.set_new_peer_dep(
- &[&optional_peer.graph_path],
- peer_parent,
- &optional_peer.specifier,
- new_id,
- );
- }
- }
- }
- None => {
- // store this for later if it's resolved for this version
- self
- .unresolved_optional_peers
- .entry(parent_nv.clone())
- .or_default()
- .push(UnresolvedOptionalPeer {
- specifier: dep.bare_specifier.clone(),
- graph_path: parent_path.clone(),
- });
- }
- }
- }
- }
- }
- }
-
- if !found_peer {
- self.graph.borrow_node_mut(parent_path.node_id()).no_peers = true;
- }
- }
- Ok(())
- }
-
- fn resolve_peer_dep(
- &mut self,
- specifier: &str,
- peer_dep: &NpmDependencyEntry,
- peer_package_info: &NpmPackageInfo,
- ancestor_path: &Arc<GraphPath>,
- ) -> Result<Option<NodeId>, AnyError> {
- debug_assert!(matches!(
- peer_dep.kind,
- NpmDependencyEntryKind::Peer | NpmDependencyEntryKind::OptionalPeer
- ));
-
- let mut path = vec![ancestor_path];
-
- // the current dependency might have had the peer dependency
- // in another bare specifier slot... if so resolve it to that
- {
- let maybe_peer_dep = self.find_peer_dep_in_node(
- ancestor_path,
- peer_dep,
- peer_package_info,
- )?;
-
- if let Some((peer_parent, peer_dep_id)) = maybe_peer_dep {
- // this will always have an ancestor because we're not at the root
- self.set_new_peer_dep(&path, peer_parent, specifier, peer_dep_id);
- return Ok(Some(peer_dep_id));
- }
- }
-
- // Peer dependencies are resolved based on its ancestors' siblings.
- // If not found, then it resolves based on the version requirement if non-optional.
- for ancestor_node in ancestor_path.ancestors() {
- match ancestor_node {
- GraphPathNodeOrRoot::Node(ancestor_graph_path_node) => {
- path.push(ancestor_graph_path_node);
- let maybe_peer_dep = self.find_peer_dep_in_node(
- ancestor_graph_path_node,
- peer_dep,
- peer_package_info,
- )?;
- if let Some((parent, peer_dep_id)) = maybe_peer_dep {
- // this will always have an ancestor because we're not at the root
- self.set_new_peer_dep(&path, parent, specifier, peer_dep_id);
- return Ok(Some(peer_dep_id));
- }
- }
- GraphPathNodeOrRoot::Root(root_pkg_id) => {
- // in this case, the parent is the root so the children are all the package requirements
- if let Some(child_id) = find_matching_child(
- peer_dep,
- peer_package_info,
- self.graph.root_packages.iter().map(|(nv, id)| (*id, nv)),
- )? {
- let peer_parent = GraphPathNodeOrRoot::Root(root_pkg_id.clone());
- self.set_new_peer_dep(&path, peer_parent, specifier, child_id);
- return Ok(Some(child_id));
- }
- }
- }
- }
-
- // We didn't find anything by searching the ancestor siblings, so we need
- // to resolve based on the package info
- if !peer_dep.kind.is_optional() {
- let parent_id = ancestor_path.node_id();
- let (_, node_id) = self.resolve_node_from_info(
- &peer_dep.name,
- peer_dep
- .peer_dep_version_req
- .as_ref()
- .unwrap_or(&peer_dep.version_req),
- peer_package_info,
- Some(parent_id),
- )?;
- let peer_parent = GraphPathNodeOrRoot::Node(ancestor_path.clone());
- self.set_new_peer_dep(&[ancestor_path], peer_parent, specifier, node_id);
- Ok(Some(node_id))
- } else {
- Ok(None)
- }
- }
-
- fn find_peer_dep_in_node(
- &self,
- path: &Arc<GraphPath>,
- peer_dep: &NpmDependencyEntry,
- peer_package_info: &NpmPackageInfo,
- ) -> Result<Option<(GraphPathNodeOrRoot, NodeId)>, AnyError> {
- let node_id = path.node_id();
- let resolved_node_id = self.graph.resolved_node_ids.get(node_id).unwrap();
- // check if this node itself is a match for
- // the peer dependency and if so use that
- if resolved_node_id.nv.name == peer_dep.name
- && version_req_satisfies(
- &peer_dep.version_req,
- &resolved_node_id.nv.version,
- peer_package_info,
- None,
- )?
- {
- let parent = path.previous_node.as_ref().unwrap().clone();
- Ok(Some((parent, node_id)))
- } else {
- let node = self.graph.nodes.get(&node_id).unwrap();
- let children = node.children.values().map(|child_node_id| {
- let child_node_id = *child_node_id;
- (
- child_node_id,
- &self.graph.resolved_node_ids.get(child_node_id).unwrap().nv,
- )
- });
- find_matching_child(peer_dep, peer_package_info, children).map(
- |maybe_child_id| {
- maybe_child_id.map(|child_id| {
- let parent = GraphPathNodeOrRoot::Node(path.clone());
- (parent, child_id)
- })
- },
- )
- }
- }
-
- fn add_peer_deps_to_path(
- &mut self,
- // path from the node above the resolved dep to just above the peer dep
- path: &[&Arc<GraphPath>],
- peer_deps: &[(&ResolvedIdPeerDep, Arc<NpmPackageNv>)],
- ) {
- debug_assert!(!path.is_empty());
-
- for graph_path_node in path.iter().rev() {
- let old_node_id = graph_path_node.node_id();
- let old_resolved_id = self
- .graph
- .resolved_node_ids
- .get(old_node_id)
- .unwrap()
- .clone();
-
- let mut new_resolved_id = old_resolved_id;
- let mut has_changed = false;
- for (peer_dep, nv) in peer_deps {
- if *nv == new_resolved_id.nv {
- continue;
- }
- if new_resolved_id.push_peer_dep((*peer_dep).clone()) {
- has_changed = true;
- }
- }
-
- if !has_changed {
- continue; // nothing to change
- }
-
- let (created, new_node_id) =
- self.graph.get_or_create_for_id(&new_resolved_id);
-
- if created {
- let old_children =
- self.graph.borrow_node_mut(old_node_id).children.clone();
- // copy over the old children to this new one
- for (specifier, child_id) in &old_children {
- self.graph.set_child_of_parent_node(
- new_node_id,
- specifier,
- *child_id,
- );
- }
- }
-
- graph_path_node.change_id(new_node_id);
-
- let circular_descendants =
- graph_path_node.linked_circular_descendants.lock().clone();
- for descendant in circular_descendants {
- let path = descendant.get_path_to_ancestor_exclusive(new_node_id);
- self.add_peer_deps_to_path(&path, peer_deps);
- descendant.change_id(new_node_id);
-
- // update the bottom node to point to this new node id
- let bottom_node_id = path[0].node_id();
- self.graph.set_child_of_parent_node(
- bottom_node_id,
- descendant.specifier(),
- descendant.node_id(),
- );
- }
-
- // update the previous parent to have this as its child
- match graph_path_node.previous_node.as_ref().unwrap() {
- GraphPathNodeOrRoot::Root(pkg_id) => {
- self.graph.root_packages.insert(pkg_id.clone(), new_node_id);
- }
- GraphPathNodeOrRoot::Node(parent_node_path) => {
- let parent_node_id = parent_node_path.node_id();
- let parent_node = self.graph.borrow_node_mut(parent_node_id);
- parent_node
- .children
- .insert(graph_path_node.specifier().to_string(), new_node_id);
- }
- }
- }
- }
-
- fn set_new_peer_dep(
- &mut self,
- // path from the node above the resolved dep to just above the peer dep
- path: &[&Arc<GraphPath>],
- peer_dep_parent: GraphPathNodeOrRoot,
- peer_dep_specifier: &str,
- peer_dep_id: NodeId,
- ) {
- debug_assert!(!path.is_empty());
- let peer_dep_nv = self
- .graph
- .resolved_node_ids
- .get(peer_dep_id)
- .unwrap()
- .nv
- .clone();
-
- let peer_dep = ResolvedIdPeerDep::ParentReference {
- parent: peer_dep_parent,
- child_pkg_nv: peer_dep_nv.clone(),
- };
-
- let top_node = path.last().unwrap();
- let (maybe_circular_ancestor, path) = if top_node.nv == peer_dep_nv {
- // it's circular, so exclude the top node
- (Some(top_node), &path[0..path.len() - 1])
- } else {
- (None, path)
- };
- self.add_peer_deps_to_path(path, &[(&peer_dep, peer_dep_nv.clone())]);
-
- // now set the peer dependency
- let bottom_node = path.first().unwrap();
- self.graph.set_child_of_parent_node(
- bottom_node.node_id(),
- peer_dep_specifier,
- peer_dep_id,
- );
-
- // queue next step
- let new_path = bottom_node.with_id(
- peer_dep_id,
- peer_dep_specifier.to_string(),
- peer_dep_nv,
- );
- if let Some(ancestor_node) = maybe_circular_ancestor {
- // it's circular, so link this in step with the ancestor node
- ancestor_node
- .linked_circular_descendants
- .lock()
- .push(new_path);
- } else {
- // mark the peer dep as needing to be analyzed
- self.pending_unresolved_nodes.push_back(new_path);
- }
-
- debug!(
- "Resolved peer dependency for {} in {} to {}",
- peer_dep_specifier,
- &self
- .graph
- .get_npm_pkg_id(bottom_node.node_id())
- .as_serialized(),
- &self.graph.get_npm_pkg_id(peer_dep_id).as_serialized(),
- );
- }
-
- fn add_linked_circular_descendant(
- &mut self,
- ancestor: &Arc<GraphPath>,
- descendant: Arc<GraphPath>,
- ) {
- let ancestor_node_id = ancestor.node_id();
- let path = descendant.get_path_to_ancestor_exclusive(ancestor_node_id);
-
- let ancestor_resolved_id = self
- .graph
- .resolved_node_ids
- .get(ancestor_node_id)
- .unwrap()
- .clone();
-
- let peer_deps = ancestor_resolved_id
- .peer_dependencies
- .iter()
- .map(|peer_dep| {
- (
- peer_dep,
- match &peer_dep {
- ResolvedIdPeerDep::ParentReference { child_pkg_nv, .. } => {
- child_pkg_nv.clone()
- }
- ResolvedIdPeerDep::SnapshotNodeId(node_id) => self
- .graph
- .resolved_node_ids
- .get(*node_id)
- .unwrap()
- .nv
- .clone(),
- },
- )
- })
- .collect::<Vec<_>>();
- if !peer_deps.is_empty() {
- self.add_peer_deps_to_path(&path, &peer_deps);
- }
-
- let bottom_node_id = path[0].node_id();
- self.graph.set_child_of_parent_node(
- bottom_node_id,
- descendant.specifier(),
- descendant.node_id(),
- );
-
- ancestor.linked_circular_descendants.lock().push(descendant);
- }
-}
-
-fn find_matching_child<'a>(
- peer_dep: &NpmDependencyEntry,
- peer_package_info: &NpmPackageInfo,
- children: impl Iterator<Item = (NodeId, &'a Arc<NpmPackageNv>)>,
-) -> Result<Option<NodeId>, AnyError> {
- for (child_id, pkg_id) in children {
- if pkg_id.name == peer_dep.name
- && version_req_satisfies(
- &peer_dep.version_req,
- &pkg_id.version,
- peer_package_info,
- None,
- )?
- {
- return Ok(Some(child_id));
- }
- }
- Ok(None)
-}
-
-#[cfg(test)]
-mod test {
- use deno_graph::npm::NpmPackageReqReference;
- use pretty_assertions::assert_eq;
-
- use crate::npm::registry::TestNpmRegistryApiInner;
-
- use super::*;
-
- #[test]
- fn resolved_id_tests() {
- let mut ids = ResolvedNodeIds::default();
- let node_id = NodeId(0);
- let resolved_id = ResolvedId {
- nv: Arc::new(NpmPackageNv::from_str("package@1.1.1").unwrap()),
- peer_dependencies: Vec::new(),
- };
- ids.set(node_id, resolved_id.clone());
- assert!(ids.get(node_id).is_some());
- assert!(ids.get(NodeId(1)).is_none());
- assert_eq!(ids.get_node_id(&resolved_id), Some(node_id));
-
- let resolved_id_new = ResolvedId {
- nv: Arc::new(NpmPackageNv::from_str("package@1.1.2").unwrap()),
- peer_dependencies: Vec::new(),
- };
- ids.set(node_id, resolved_id_new.clone());
- assert_eq!(ids.get_node_id(&resolved_id), None); // stale entry should have been removed
- assert!(ids.get(node_id).is_some());
- assert_eq!(ids.get_node_id(&resolved_id_new), Some(node_id));
- }
-
- #[tokio::test]
- async fn resolve_deps_no_peer() {
- let api = TestNpmRegistryApiInner::default();
- api.ensure_package_version("package-a", "1.0.0");
- api.ensure_package_version("package-b", "2.0.0");
- api.ensure_package_version("package-c", "0.1.0");
- api.ensure_package_version("package-c", "0.0.10");
- api.ensure_package_version("package-d", "3.2.1");
- api.ensure_package_version("package-d", "3.2.0");
- api.add_dependency(("package-a", "1.0.0"), ("package-b", "^2"));
- api.add_dependency(("package-a", "1.0.0"), ("package-c", "^0.1"));
- api.add_dependency(("package-c", "0.1.0"), ("package-d", "*"));
-
- let (packages, package_reqs) =
- run_resolver_and_get_output(api, vec!["npm:package-a@1"]).await;
- assert_eq!(
- packages,
- vec![
- TestNpmResolutionPackage {
- pkg_id: "package-a@1.0.0".to_string(),
- copy_index: 0,
- dependencies: BTreeMap::from([
- ("package-b".to_string(), "package-b@2.0.0".to_string(),),
- ("package-c".to_string(), "package-c@0.1.0".to_string(),),
- ]),
- },
- TestNpmResolutionPackage {
- pkg_id: "package-b@2.0.0".to_string(),
- copy_index: 0,
- dependencies: Default::default(),
- },
- TestNpmResolutionPackage {
- pkg_id: "package-c@0.1.0".to_string(),
- copy_index: 0,
- dependencies: BTreeMap::from([(
- "package-d".to_string(),
- "package-d@3.2.1".to_string(),
- )])
- },
- TestNpmResolutionPackage {
- pkg_id: "package-d@3.2.1".to_string(),
- copy_index: 0,
- dependencies: Default::default(),
- },
- ]
- );
- assert_eq!(
- package_reqs,
- vec![("package-a@1".to_string(), "package-a@1.0.0".to_string())]
- );
- }
-
- #[tokio::test]
- async fn resolve_deps_circular() {
- let api = TestNpmRegistryApiInner::default();
- api.ensure_package_version("package-a", "1.0.0");
- api.ensure_package_version("package-b", "2.0.0");
- api.add_dependency(("package-a", "1.0.0"), ("package-b", "*"));
- api.add_dependency(("package-b", "2.0.0"), ("package-a", "1"));
-
- let (packages, package_reqs) =
- run_resolver_and_get_output(api, vec!["npm:package-a@1.0"]).await;
- assert_eq!(
- packages,
- vec![
- TestNpmResolutionPackage {
- pkg_id: "package-a@1.0.0".to_string(),
- copy_index: 0,
- dependencies: BTreeMap::from([(
- "package-b".to_string(),
- "package-b@2.0.0".to_string(),
- )]),
- },
- TestNpmResolutionPackage {
- pkg_id: "package-b@2.0.0".to_string(),
- copy_index: 0,
- dependencies: BTreeMap::from([(
- "package-a".to_string(),
- "package-a@1.0.0".to_string(),
- )]),
- },
- ]
- );
- assert_eq!(
- package_reqs,
- vec![("package-a@1.0".to_string(), "package-a@1.0.0".to_string())]
- );
- }
-
- #[tokio::test]
- async fn peer_deps_simple_top_tree() {
- let api = TestNpmRegistryApiInner::default();
- api.ensure_package_version("package-a", "1.0.0");
- api.ensure_package_version("package-b", "1.0.0");
- api.ensure_package_version("package-peer", "1.0.0");
- api.add_dependency(("package-a", "1.0.0"), ("package-b", "1"));
- api.add_peer_dependency(("package-b", "1.0.0"), ("package-peer", "*"));
-
- let (packages, package_reqs) = run_resolver_and_get_output(
- api,
- vec!["npm:package-a@1.0", "npm:package-peer@1.0"],
- )
- .await;
- assert_eq!(
- packages,
- vec![
- TestNpmResolutionPackage {
- pkg_id: "package-a@1.0.0_package-peer@1.0.0".to_string(),
- copy_index: 0,
- dependencies: BTreeMap::from([(
- "package-b".to_string(),
- "package-b@1.0.0_package-peer@1.0.0".to_string(),
- )]),
- },
- TestNpmResolutionPackage {
- pkg_id: "package-b@1.0.0_package-peer@1.0.0".to_string(),
- copy_index: 0,
- dependencies: BTreeMap::from([(
- "package-peer".to_string(),
- "package-peer@1.0.0".to_string(),
- )]),
- },
- TestNpmResolutionPackage {
- pkg_id: "package-peer@1.0.0".to_string(),
- copy_index: 0,
- dependencies: Default::default(),
- }
- ]
- );
- assert_eq!(
- package_reqs,
- vec![
- (
- "package-a@1.0".to_string(),
- "package-a@1.0.0_package-peer@1.0.0".to_string()
- ),
- (
- "package-peer@1.0".to_string(),
- "package-peer@1.0.0".to_string()
- )
- ]
- );
- }
-
- #[tokio::test]
- async fn peer_deps_simple_root_pkg_children() {
- let api = TestNpmRegistryApiInner::default();
- api.ensure_package_version("package-0", "1.0.0");
- api.ensure_package_version("package-a", "1.0.0");
- api.ensure_package_version("package-b", "1.0.0");
- api.ensure_package_version("package-peer", "1.0.0");
- api.add_dependency(("package-0", "1.0.0"), ("package-a", "1"));
- api.add_dependency(("package-0", "1.0.0"), ("package-peer", "1"));
- api.add_dependency(("package-a", "1.0.0"), ("package-b", "1"));
- api.add_peer_dependency(("package-b", "1.0.0"), ("package-peer", "*"));
-
- let (packages, package_reqs) =
- run_resolver_and_get_output(api, vec!["npm:package-0@1.0"]).await;
- assert_eq!(
- packages,
- vec![
- TestNpmResolutionPackage {
- pkg_id: "package-0@1.0.0_package-peer@1.0.0".to_string(),
- copy_index: 0,
- dependencies: BTreeMap::from([
- (
- "package-a".to_string(),
- "package-a@1.0.0_package-peer@1.0.0".to_string(),
- ),
- ("package-peer".to_string(), "package-peer@1.0.0".to_string(),)
- ]),
- },
- TestNpmResolutionPackage {
- pkg_id: "package-a@1.0.0_package-peer@1.0.0".to_string(),
- copy_index: 0,
- dependencies: BTreeMap::from([(
- "package-b".to_string(),
- "package-b@1.0.0_package-peer@1.0.0".to_string(),
- )]),
- },
- TestNpmResolutionPackage {
- pkg_id: "package-b@1.0.0_package-peer@1.0.0".to_string(),
- copy_index: 0,
- dependencies: BTreeMap::from([(
- "package-peer".to_string(),
- "package-peer@1.0.0".to_string(),
- )]),
- },
- TestNpmResolutionPackage {
- pkg_id: "package-peer@1.0.0".to_string(),
- copy_index: 0,
- dependencies: Default::default(),
- }
- ]
- );
- assert_eq!(
- package_reqs,
- vec![(
- "package-0@1.0".to_string(),
- "package-0@1.0.0_package-peer@1.0.0".to_string()
- ),]
- );
- }
-
- #[tokio::test]
- async fn peer_deps_simple_deeper() {
- let api = TestNpmRegistryApiInner::default();
- api.ensure_package_version("package-0", "1.0.0");
- api.ensure_package_version("package-1", "1.0.0");
- api.ensure_package_version("package-a", "1.0.0");
- api.ensure_package_version("package-b", "1.0.0");
- api.ensure_package_version("package-peer", "1.0.0");
- api.add_dependency(("package-0", "1.0.0"), ("package-1", "1"));
- api.add_dependency(("package-1", "1.0.0"), ("package-a", "1"));
- api.add_dependency(("package-1", "1.0.0"), ("package-peer", "1"));
- api.add_dependency(("package-a", "1.0.0"), ("package-b", "1"));
- api.add_peer_dependency(("package-b", "1.0.0"), ("package-peer", "*"));
-
- let (packages, package_reqs) =
- run_resolver_and_get_output(api, vec!["npm:package-0@1.0"]).await;
- assert_eq!(
- packages,
- vec![
- TestNpmResolutionPackage {
- pkg_id: "package-0@1.0.0".to_string(),
- copy_index: 0,
- dependencies: BTreeMap::from([(
- "package-1".to_string(),
- "package-1@1.0.0_package-peer@1.0.0".to_string(),
- )]),
- },
- TestNpmResolutionPackage {
- pkg_id: "package-1@1.0.0_package-peer@1.0.0".to_string(),
- copy_index: 0,
- dependencies: BTreeMap::from([
- (
- "package-a".to_string(),
- "package-a@1.0.0_package-peer@1.0.0".to_string(),
- ),
- ("package-peer".to_string(), "package-peer@1.0.0".to_string(),)
- ]),
- },
- TestNpmResolutionPackage {
- pkg_id: "package-a@1.0.0_package-peer@1.0.0".to_string(),
- copy_index: 0,
- dependencies: BTreeMap::from([(
- "package-b".to_string(),
- "package-b@1.0.0_package-peer@1.0.0".to_string(),
- )]),
- },
- TestNpmResolutionPackage {
- pkg_id: "package-b@1.0.0_package-peer@1.0.0".to_string(),
- copy_index: 0,
- dependencies: BTreeMap::from([(
- "package-peer".to_string(),
- "package-peer@1.0.0".to_string(),
- )]),
- },
- TestNpmResolutionPackage {
- pkg_id: "package-peer@1.0.0".to_string(),
- copy_index: 0,
- dependencies: Default::default(),
- }
- ]
- );
- assert_eq!(
- package_reqs,
- vec![("package-0@1.0".to_string(), "package-0@1.0.0".to_string()),]
- );
- }
-
- #[tokio::test]
- async fn resolve_with_peer_deps_top_tree() {
- let api = TestNpmRegistryApiInner::default();
- api.ensure_package_version("package-a", "1.0.0");
- api.ensure_package_version("package-b", "2.0.0");
- api.ensure_package_version("package-c", "3.0.0");
- api.ensure_package_version("package-peer", "4.0.0");
- api.ensure_package_version("package-peer", "4.1.0");
- api.add_dependency(("package-a", "1.0.0"), ("package-b", "^2"));
- api.add_dependency(("package-a", "1.0.0"), ("package-c", "^3"));
- api.add_peer_dependency(("package-b", "2.0.0"), ("package-peer", "4"));
- api.add_peer_dependency(("package-c", "3.0.0"), ("package-peer", "*"));
-
- let (packages, package_reqs) = run_resolver_and_get_output(
- api,
- // the peer dependency is specified here at the top of the tree
- // so it should resolve to 4.0.0 instead of 4.1.0
- vec!["npm:package-a@1", "npm:package-peer@4.0.0"],
- )
- .await;
- assert_eq!(
- packages,
- vec![
- TestNpmResolutionPackage {
- pkg_id: "package-a@1.0.0_package-peer@4.0.0".to_string(),
- copy_index: 0,
- dependencies: BTreeMap::from([
- (
- "package-b".to_string(),
- "package-b@2.0.0_package-peer@4.0.0".to_string(),
- ),
- (
- "package-c".to_string(),
- "package-c@3.0.0_package-peer@4.0.0".to_string(),
- ),
- ]),
- },
- TestNpmResolutionPackage {
- pkg_id: "package-b@2.0.0_package-peer@4.0.0".to_string(),
- copy_index: 0,
- dependencies: BTreeMap::from([(
- "package-peer".to_string(),
- "package-peer@4.0.0".to_string(),
- )])
- },
- TestNpmResolutionPackage {
- pkg_id: "package-c@3.0.0_package-peer@4.0.0".to_string(),
- copy_index: 0,
- dependencies: BTreeMap::from([(
- "package-peer".to_string(),
- "package-peer@4.0.0".to_string(),
- )])
- },
- TestNpmResolutionPackage {
- pkg_id: "package-peer@4.0.0".to_string(),
- copy_index: 0,
- dependencies: Default::default(),
- },
- ]
- );
- assert_eq!(
- package_reqs,
- vec![
- (
- "package-a@1".to_string(),
- "package-a@1.0.0_package-peer@4.0.0".to_string()
- ),
- (
- "package-peer@4.0.0".to_string(),
- "package-peer@4.0.0".to_string()
- )
- ]
- );
- }
-
- #[tokio::test]
- async fn resolve_with_peer_deps_ancestor_sibling_not_top_tree() {
- let api = TestNpmRegistryApiInner::default();
- api.ensure_package_version("package-0", "1.1.1");
- api.ensure_package_version("package-a", "1.0.0");
- api.ensure_package_version("package-b", "2.0.0");
- api.ensure_package_version("package-c", "3.0.0");
- api.ensure_package_version("package-peer", "4.0.0");
- api.ensure_package_version("package-peer", "4.1.0");
- api.add_dependency(("package-0", "1.1.1"), ("package-a", "1"));
- api.add_dependency(("package-a", "1.0.0"), ("package-b", "^2"));
- api.add_dependency(("package-a", "1.0.0"), ("package-c", "^3"));
- // the peer dependency is specified here as a sibling of "a" and "b"
- // so it should resolve to 4.0.0 instead of 4.1.0
- api.add_dependency(("package-a", "1.0.0"), ("package-peer", "4.0.0"));
- api.add_peer_dependency(("package-b", "2.0.0"), ("package-peer", "4"));
- api.add_peer_dependency(("package-c", "3.0.0"), ("package-peer", "*"));
-
- let (packages, package_reqs) =
- run_resolver_and_get_output(api, vec!["npm:package-0@1.1.1"]).await;
- assert_eq!(
- packages,
- vec![
- TestNpmResolutionPackage {
- pkg_id: "package-0@1.1.1".to_string(),
- copy_index: 0,
- dependencies: BTreeMap::from([(
- "package-a".to_string(),
- "package-a@1.0.0_package-peer@4.0.0".to_string(),
- ),]),
- },
- TestNpmResolutionPackage {
- pkg_id: "package-a@1.0.0_package-peer@4.0.0".to_string(),
- copy_index: 0,
- dependencies: BTreeMap::from([
- (
- "package-b".to_string(),
- "package-b@2.0.0_package-peer@4.0.0".to_string(),
- ),
- (
- "package-c".to_string(),
- "package-c@3.0.0_package-peer@4.0.0".to_string(),
- ),
- ("package-peer".to_string(), "package-peer@4.0.0".to_string(),),
- ]),
- },
- TestNpmResolutionPackage {
- pkg_id: "package-b@2.0.0_package-peer@4.0.0".to_string(),
- copy_index: 0,
- dependencies: BTreeMap::from([(
- "package-peer".to_string(),
- "package-peer@4.0.0".to_string(),
- )])
- },
- TestNpmResolutionPackage {
- pkg_id: "package-c@3.0.0_package-peer@4.0.0".to_string(),
- copy_index: 0,
- dependencies: BTreeMap::from([(
- "package-peer".to_string(),
- "package-peer@4.0.0".to_string(),
- )])
- },
- TestNpmResolutionPackage {
- pkg_id: "package-peer@4.0.0".to_string(),
- copy_index: 0,
- dependencies: Default::default(),
- },
- ]
- );
- assert_eq!(
- package_reqs,
- vec![("package-0@1.1.1".to_string(), "package-0@1.1.1".to_string())]
- );
- }
-
- #[tokio::test]
- async fn resolve_with_peer_deps_auto_resolved() {
- // in this case, the peer dependency is not found in the tree
- // so it's auto-resolved based on the registry
- let api = TestNpmRegistryApiInner::default();
- api.ensure_package_version("package-a", "1.0.0");
- api.ensure_package_version("package-b", "2.0.0");
- api.ensure_package_version("package-c", "3.0.0");
- api.ensure_package_version("package-peer", "4.0.0");
- api.ensure_package_version("package-peer", "4.1.0");
- api.add_dependency(("package-a", "1.0.0"), ("package-b", "^2"));
- api.add_dependency(("package-a", "1.0.0"), ("package-c", "^3"));
- api.add_peer_dependency(("package-b", "2.0.0"), ("package-peer", "4"));
- api.add_peer_dependency(("package-c", "3.0.0"), ("package-peer", "*"));
-
- let (packages, package_reqs) =
- run_resolver_and_get_output(api, vec!["npm:package-a@1"]).await;
- assert_eq!(
- packages,
- vec![
- TestNpmResolutionPackage {
- pkg_id: "package-a@1.0.0".to_string(),
- copy_index: 0,
- dependencies: BTreeMap::from([
- (
- "package-b".to_string(),
- "package-b@2.0.0_package-peer@4.1.0".to_string(),
- ),
- (
- "package-c".to_string(),
- "package-c@3.0.0_package-peer@4.1.0".to_string(),
- ),
- ]),
- },
- TestNpmResolutionPackage {
- pkg_id: "package-b@2.0.0_package-peer@4.1.0".to_string(),
- copy_index: 0,
- dependencies: BTreeMap::from([(
- "package-peer".to_string(),
- "package-peer@4.1.0".to_string(),
- )])
- },
- TestNpmResolutionPackage {
- pkg_id: "package-c@3.0.0_package-peer@4.1.0".to_string(),
- copy_index: 0,
- dependencies: BTreeMap::from([(
- "package-peer".to_string(),
- "package-peer@4.1.0".to_string(),
- )])
- },
- TestNpmResolutionPackage {
- pkg_id: "package-peer@4.1.0".to_string(),
- copy_index: 0,
- dependencies: Default::default(),
- },
- ]
- );
- assert_eq!(
- package_reqs,
- vec![("package-a@1".to_string(), "package-a@1.0.0".to_string())]
- );
- }
-
- #[tokio::test]
- async fn resolve_with_optional_peer_dep_not_resolved() {
- // in this case, the peer dependency is not found in the tree
- // so it's auto-resolved based on the registry
- let api = TestNpmRegistryApiInner::default();
- api.ensure_package_version("package-a", "1.0.0");
- api.ensure_package_version("package-b", "2.0.0");
- api.ensure_package_version("package-c", "3.0.0");
- api.ensure_package_version("package-peer", "4.0.0");
- api.ensure_package_version("package-peer", "4.1.0");
- api.add_dependency(("package-a", "1.0.0"), ("package-b", "^2"));
- api.add_dependency(("package-a", "1.0.0"), ("package-c", "^3"));
- api.add_optional_peer_dependency(
- ("package-b", "2.0.0"),
- ("package-peer", "4"),
- );
- api.add_optional_peer_dependency(
- ("package-c", "3.0.0"),
- ("package-peer", "*"),
- );
-
- let (packages, package_reqs) =
- run_resolver_and_get_output(api, vec!["npm:package-a@1"]).await;
- assert_eq!(
- packages,
- vec![
- TestNpmResolutionPackage {
- pkg_id: "package-a@1.0.0".to_string(),
- copy_index: 0,
- dependencies: BTreeMap::from([
- ("package-b".to_string(), "package-b@2.0.0".to_string(),),
- ("package-c".to_string(), "package-c@3.0.0".to_string(),),
- ]),
- },
- TestNpmResolutionPackage {
- pkg_id: "package-b@2.0.0".to_string(),
- copy_index: 0,
- dependencies: Default::default(),
- },
- TestNpmResolutionPackage {
- pkg_id: "package-c@3.0.0".to_string(),
- copy_index: 0,
- dependencies: Default::default(),
- },
- ]
- );
- assert_eq!(
- package_reqs,
- vec![("package-a@1".to_string(), "package-a@1.0.0".to_string())]
- );
- }
-
- #[tokio::test]
- async fn resolve_with_optional_peer_found() {
- let api = TestNpmRegistryApiInner::default();
- api.ensure_package_version("package-a", "1.0.0");
- api.ensure_package_version("package-b", "2.0.0");
- api.ensure_package_version("package-c", "3.0.0");
- api.ensure_package_version("package-peer", "4.0.0");
- api.ensure_package_version("package-peer", "4.1.0");
- api.add_dependency(("package-a", "1.0.0"), ("package-b", "^2"));
- api.add_dependency(("package-a", "1.0.0"), ("package-c", "^3"));
- api.add_optional_peer_dependency(
- ("package-b", "2.0.0"),
- ("package-peer", "4"),
- );
- api.add_optional_peer_dependency(
- ("package-c", "3.0.0"),
- ("package-peer", "*"),
- );
-
- let (packages, package_reqs) = run_resolver_and_get_output(
- api,
- vec!["npm:package-a@1", "npm:package-peer@4.0.0"],
- )
- .await;
- assert_eq!(
- packages,
- vec![
- TestNpmResolutionPackage {
- pkg_id: "package-a@1.0.0_package-peer@4.0.0".to_string(),
- copy_index: 0,
- dependencies: BTreeMap::from([
- (
- "package-b".to_string(),
- "package-b@2.0.0_package-peer@4.0.0".to_string(),
- ),
- (
- "package-c".to_string(),
- "package-c@3.0.0_package-peer@4.0.0".to_string(),
- ),
- ]),
- },
- TestNpmResolutionPackage {
- pkg_id: "package-b@2.0.0_package-peer@4.0.0".to_string(),
- copy_index: 0,
- dependencies: BTreeMap::from([(
- "package-peer".to_string(),
- "package-peer@4.0.0".to_string(),
- )])
- },
- TestNpmResolutionPackage {
- pkg_id: "package-c@3.0.0_package-peer@4.0.0".to_string(),
- copy_index: 0,
- dependencies: BTreeMap::from([(
- "package-peer".to_string(),
- "package-peer@4.0.0".to_string(),
- )])
- },
- TestNpmResolutionPackage {
- pkg_id: "package-peer@4.0.0".to_string(),
- copy_index: 0,
- dependencies: Default::default(),
- },
- ]
- );
- assert_eq!(
- package_reqs,
- vec![
- (
- "package-a@1".to_string(),
- "package-a@1.0.0_package-peer@4.0.0".to_string()
- ),
- (
- "package-peer@4.0.0".to_string(),
- "package-peer@4.0.0".to_string()
- )
- ]
- );
- }
-
- #[tokio::test]
- async fn resolve_optional_peer_first_not_resolved_second_resolved_scenario1()
- {
- // When resolving a dependency a second time and it has an optional
- // peer dependency that wasn't previously resolved, it should resolve all the
- // previous versions to the new one
- let api = TestNpmRegistryApiInner::default();
- api.ensure_package_version("package-a", "1.0.0");
- api.ensure_package_version("package-b", "1.0.0");
- api.ensure_package_version("package-peer", "1.0.0");
- api.add_dependency(("package-a", "1.0.0"), ("package-b", "^1"));
- api.add_dependency(("package-a", "1.0.0"), ("package-peer", "^1"));
- api.add_optional_peer_dependency(
- ("package-b", "1.0.0"),
- ("package-peer", "*"),
- );
-
- let (packages, package_reqs) = run_resolver_and_get_output(
- api,
- vec!["npm:package-a@1", "npm:package-b@1"],
- )
- .await;
- assert_eq!(
- packages,
- vec![
- TestNpmResolutionPackage {
- pkg_id: "package-a@1.0.0_package-peer@1.0.0".to_string(),
- copy_index: 0,
- dependencies: BTreeMap::from([
- (
- "package-b".to_string(),
- "package-b@1.0.0_package-peer@1.0.0".to_string(),
- ),
- ("package-peer".to_string(), "package-peer@1.0.0".to_string(),),
- ]),
- },
- TestNpmResolutionPackage {
- pkg_id: "package-b@1.0.0_package-peer@1.0.0".to_string(),
- copy_index: 0,
- dependencies: BTreeMap::from([(
- "package-peer".to_string(),
- "package-peer@1.0.0".to_string(),
- )]),
- },
- TestNpmResolutionPackage {
- pkg_id: "package-peer@1.0.0".to_string(),
- copy_index: 0,
- dependencies: Default::default(),
- },
- ]
- );
- assert_eq!(
- package_reqs,
- vec![
- (
- "package-a@1".to_string(),
- "package-a@1.0.0_package-peer@1.0.0".to_string()
- ),
- (
- "package-b@1".to_string(),
- "package-b@1.0.0_package-peer@1.0.0".to_string()
- )
- ]
- );
- }
-
- #[tokio::test]
- async fn resolve_optional_peer_first_not_resolved_second_resolved_scenario2()
- {
- let api = TestNpmRegistryApiInner::default();
- api.ensure_package_version("package-a", "1.0.0");
- api.ensure_package_version("package-b", "1.0.0");
- api.ensure_package_version("package-peer", "2.0.0");
- api.add_optional_peer_dependency(
- ("package-a", "1.0.0"),
- ("package-peer", "*"),
- );
- api.add_dependency(("package-b", "1.0.0"), ("package-a", "1.0.0"));
- api.add_dependency(("package-b", "1.0.0"), ("package-peer", "2.0.0"));
-
- let (packages, package_reqs) = run_resolver_and_get_output(
- api,
- vec!["npm:package-a@1", "npm:package-b@1"],
- )
- .await;
- assert_eq!(
- packages,
- vec![
- TestNpmResolutionPackage {
- pkg_id: "package-a@1.0.0_package-peer@2.0.0".to_string(),
- copy_index: 0,
- dependencies: BTreeMap::from([(
- "package-peer".to_string(),
- "package-peer@2.0.0".to_string(),
- )]),
- },
- TestNpmResolutionPackage {
- pkg_id: "package-b@1.0.0_package-peer@2.0.0".to_string(),
- copy_index: 0,
- dependencies: BTreeMap::from([
- (
- "package-a".to_string(),
- "package-a@1.0.0_package-peer@2.0.0".to_string(),
- ),
- ("package-peer".to_string(), "package-peer@2.0.0".to_string(),)
- ]),
- },
- TestNpmResolutionPackage {
- pkg_id: "package-peer@2.0.0".to_string(),
- copy_index: 0,
- dependencies: Default::default(),
- },
- ]
- );
- assert_eq!(
- package_reqs,
- vec![
- (
- "package-a@1".to_string(),
- "package-a@1.0.0_package-peer@2.0.0".to_string()
- ),
- (
- "package-b@1".to_string(),
- "package-b@1.0.0_package-peer@2.0.0".to_string()
- )
- ]
- );
- }
-
- #[tokio::test]
- async fn resolve_optional_dep_npm_req_top() {
- let api = TestNpmRegistryApiInner::default();
- api.ensure_package_version("package-a", "1.0.0");
- api.ensure_package_version("package-peer", "1.0.0");
- api.add_optional_peer_dependency(
- ("package-a", "1.0.0"),
- ("package-peer", "*"),
- );
-
- let (packages, package_reqs) = run_resolver_and_get_output(
- api,
- vec!["npm:package-a@1", "npm:package-peer@1"],
- )
- .await;
- assert_eq!(
- packages,
- vec![
- TestNpmResolutionPackage {
- pkg_id: "package-a@1.0.0_package-peer@1.0.0".to_string(),
- copy_index: 0,
- dependencies: BTreeMap::from([(
- "package-peer".to_string(),
- "package-peer@1.0.0".to_string(),
- )]),
- },
- TestNpmResolutionPackage {
- pkg_id: "package-peer@1.0.0".to_string(),
- copy_index: 0,
- dependencies: Default::default(),
- },
- ]
- );
- assert_eq!(
- package_reqs,
- vec![
- (
- "package-a@1".to_string(),
- "package-a@1.0.0_package-peer@1.0.0".to_string()
- ),
- (
- "package-peer@1".to_string(),
- "package-peer@1.0.0".to_string()
- )
- ]
- );
- }
-
- #[tokio::test]
- async fn resolve_optional_dep_different_resolution_second_time() {
- let api = TestNpmRegistryApiInner::default();
- api.ensure_package_version("package-a", "1.0.0");
- api.ensure_package_version("package-b", "1.0.0");
- api.ensure_package_version("package-peer", "1.0.0");
- api.ensure_package_version("package-peer", "2.0.0");
- api.add_optional_peer_dependency(
- ("package-a", "1.0.0"),
- ("package-peer", "*"),
- );
- api.add_dependency(("package-b", "1.0.0"), ("package-a", "1.0.0"));
- api.add_dependency(("package-b", "1.0.0"), ("package-peer", "2.0.0"));
-
- let (packages, package_reqs) = run_resolver_and_get_output(
- api,
- vec![
- "npm:package-a@1",
- "npm:package-b@1",
- "npm:package-peer@1.0.0",
- ],
- )
- .await;
- assert_eq!(
- packages,
- vec![
- TestNpmResolutionPackage {
- pkg_id: "package-a@1.0.0_package-peer@1.0.0".to_string(),
- copy_index: 0,
- dependencies: BTreeMap::from([(
- "package-peer".to_string(),
- "package-peer@1.0.0".to_string(),
- )]),
- },
- TestNpmResolutionPackage {
- pkg_id: "package-a@1.0.0_package-peer@2.0.0".to_string(),
- copy_index: 1,
- dependencies: BTreeMap::from([(
- "package-peer".to_string(),
- "package-peer@2.0.0".to_string(),
- )]),
- },
- TestNpmResolutionPackage {
- pkg_id: "package-b@1.0.0_package-peer@2.0.0".to_string(),
- copy_index: 0,
- dependencies: BTreeMap::from([
- ("package-peer".to_string(), "package-peer@2.0.0".to_string(),),
- (
- "package-a".to_string(),
- "package-a@1.0.0_package-peer@2.0.0".to_string(),
- ),
- ]),
- },
- TestNpmResolutionPackage {
- pkg_id: "package-peer@1.0.0".to_string(),
- copy_index: 0,
- dependencies: Default::default(),
- },
- TestNpmResolutionPackage {
- pkg_id: "package-peer@2.0.0".to_string(),
- copy_index: 0,
- dependencies: Default::default(),
- },
- ]
- );
- assert_eq!(
- package_reqs,
- vec![
- (
- "package-a@1".to_string(),
- "package-a@1.0.0_package-peer@1.0.0".to_string()
- ),
- (
- "package-b@1".to_string(),
- "package-b@1.0.0_package-peer@2.0.0".to_string()
- ),
- (
- "package-peer@1.0.0".to_string(),
- "package-peer@1.0.0".to_string()
- )
- ]
- );
- }
- #[tokio::test]
- async fn resolve_peer_dep_other_specifier_slot() {
- let api = TestNpmRegistryApiInner::default();
- api.ensure_package_version("package-a", "1.0.0");
- api.ensure_package_version("package-peer", "2.0.0");
- // bit of an edge case... probably nobody has ever done this
- api.add_dependency(
- ("package-a", "1.0.0"),
- ("package-peer2", "npm:package-peer@2"),
- );
- api.add_peer_dependency(("package-a", "1.0.0"), ("package-peer", "2"));
-
- let (packages, package_reqs) =
- run_resolver_and_get_output(api, vec!["npm:package-a@1"]).await;
- assert_eq!(
- packages,
- vec![
- TestNpmResolutionPackage {
- pkg_id: "package-a@1.0.0_package-peer@2.0.0".to_string(),
- copy_index: 0,
- dependencies: BTreeMap::from([
- ("package-peer".to_string(), "package-peer@2.0.0".to_string(),),
- (
- "package-peer2".to_string(),
- "package-peer@2.0.0".to_string(),
- ),
- ]),
- },
- TestNpmResolutionPackage {
- pkg_id: "package-peer@2.0.0".to_string(),
- copy_index: 0,
- dependencies: Default::default(),
- },
- ]
- );
- assert_eq!(
- package_reqs,
- vec![(
- "package-a@1".to_string(),
- "package-a@1.0.0_package-peer@2.0.0".to_string()
- ),]
- );
- }
-
- #[tokio::test]
- async fn resolve_nested_peer_deps_auto_resolved() {
- let api = TestNpmRegistryApiInner::default();
- api.ensure_package_version("package-0", "1.0.0");
- api.ensure_package_version("package-peer-a", "2.0.0");
- api.ensure_package_version("package-peer-b", "3.0.0");
- api.add_peer_dependency(("package-0", "1.0.0"), ("package-peer-a", "2"));
- api.add_peer_dependency(
- ("package-peer-a", "2.0.0"),
- ("package-peer-b", "3"),
- );
-
- let (packages, package_reqs) =
- run_resolver_and_get_output(api, vec!["npm:package-0@1.0"]).await;
- assert_eq!(
- packages,
- vec![
- TestNpmResolutionPackage {
- pkg_id: "package-0@1.0.0_package-peer-a@2.0.0__package-peer-b@3.0.0"
- .to_string(),
- copy_index: 0,
- dependencies: BTreeMap::from([(
- "package-peer-a".to_string(),
- "package-peer-a@2.0.0_package-peer-b@3.0.0".to_string(),
- )]),
- },
- TestNpmResolutionPackage {
- pkg_id: "package-peer-a@2.0.0_package-peer-b@3.0.0".to_string(),
- copy_index: 0,
- dependencies: BTreeMap::from([(
- "package-peer-b".to_string(),
- "package-peer-b@3.0.0".to_string(),
- )]),
- },
- TestNpmResolutionPackage {
- pkg_id: "package-peer-b@3.0.0".to_string(),
- copy_index: 0,
- dependencies: Default::default(),
- },
- ]
- );
- assert_eq!(
- package_reqs,
- vec![(
- "package-0@1.0".to_string(),
- "package-0@1.0.0_package-peer-a@2.0.0__package-peer-b@3.0.0"
- .to_string()
- )]
- );
- }
-
- #[tokio::test]
- async fn resolve_nested_peer_deps_ancestor_sibling_deps() {
- let api = TestNpmRegistryApiInner::default();
- api.ensure_package_version("package-0", "1.0.0");
- api.ensure_package_version("package-peer-a", "2.0.0");
- api.ensure_package_version("package-peer-b", "3.0.0");
- api.add_dependency(("package-0", "1.0.0"), ("package-peer-b", "*"));
- api.add_peer_dependency(("package-0", "1.0.0"), ("package-peer-a", "2"));
- api.add_peer_dependency(
- ("package-peer-a", "2.0.0"),
- ("package-peer-b", "3"),
- );
-
- let (packages, package_reqs) = run_resolver_and_get_output(
- api,
- vec![
- "npm:package-0@1.0",
- "npm:package-peer-a@2",
- "npm:package-peer-b@3",
- ],
- )
- .await;
- assert_eq!(
- packages,
- vec![
- TestNpmResolutionPackage {
- pkg_id: "package-0@1.0.0_package-peer-a@2.0.0__package-peer-b@3.0.0_package-peer-b@3.0.0".to_string(),
- copy_index: 0,
- dependencies: BTreeMap::from([
- (
- "package-peer-a".to_string(),
- "package-peer-a@2.0.0_package-peer-b@3.0.0".to_string(),
- ),
- (
- "package-peer-b".to_string(),
- "package-peer-b@3.0.0".to_string(),
- )
- ]),
-
- },
- TestNpmResolutionPackage {
- pkg_id: "package-peer-a@2.0.0_package-peer-b@3.0.0".to_string(),
- copy_index: 0,
- dependencies: BTreeMap::from([(
- "package-peer-b".to_string(),
- "package-peer-b@3.0.0".to_string(),
- )]),
- },
- TestNpmResolutionPackage {
- pkg_id: "package-peer-b@3.0.0".to_string(),
- copy_index: 0,
- dependencies: Default::default(),
-
- },
- ]
- );
- assert_eq!(
- package_reqs,
- vec![
- (
- "package-0@1.0".to_string(),
- "package-0@1.0.0_package-peer-a@2.0.0__package-peer-b@3.0.0_package-peer-b@3.0.0"
- .to_string()
- ),
- (
- "package-peer-a@2".to_string(),
- "package-peer-a@2.0.0_package-peer-b@3.0.0".to_string()
- ),
- (
- "package-peer-b@3".to_string(),
- "package-peer-b@3.0.0".to_string()
- )
- ]
- );
- }
-
- #[tokio::test]
- async fn resolve_with_peer_deps_multiple() {
- let api = TestNpmRegistryApiInner::default();
- api.ensure_package_version("package-0", "1.1.1");
- api.ensure_package_version("package-a", "1.0.0");
- api.ensure_package_version("package-b", "2.0.0");
- api.ensure_package_version("package-c", "3.0.0");
- api.ensure_package_version("package-d", "3.5.0");
- api.ensure_package_version("package-e", "3.6.0");
- api.ensure_package_version("package-peer-a", "4.0.0");
- api.ensure_package_version("package-peer-a", "4.1.0");
- api.ensure_package_version("package-peer-b", "5.3.0");
- api.ensure_package_version("package-peer-b", "5.4.1");
- api.ensure_package_version("package-peer-c", "6.2.0");
- api.add_dependency(("package-0", "1.1.1"), ("package-a", "1"));
- api.add_dependency(("package-a", "1.0.0"), ("package-b", "^2"));
- api.add_dependency(("package-a", "1.0.0"), ("package-c", "^3"));
- api.add_dependency(("package-a", "1.0.0"), ("package-d", "^3"));
- api.add_dependency(("package-a", "1.0.0"), ("package-peer-a", "4.0.0"));
- api.add_peer_dependency(("package-b", "2.0.0"), ("package-peer-a", "4"));
- api.add_peer_dependency(
- ("package-b", "2.0.0"),
- ("package-peer-c", "=6.2.0"), // will be auto-resolved
- );
- api.add_peer_dependency(("package-c", "3.0.0"), ("package-peer-a", "*"));
- api.add_peer_dependency(
- ("package-peer-a", "4.0.0"),
- ("package-peer-b", "^5.4"), // will be auto-resolved
- );
-
- let (packages, package_reqs) = run_resolver_and_get_output(
- api,
- vec!["npm:package-0@1.1.1", "npm:package-e@3"],
- )
- .await;
- assert_eq!(
- packages,
- vec![
- TestNpmResolutionPackage {
- pkg_id: "package-0@1.1.1".to_string(),
- copy_index: 0,
- dependencies: BTreeMap::from([(
- "package-a".to_string(),
- "package-a@1.0.0_package-peer-a@4.0.0__package-peer-b@5.4.1".to_string(),
- ),]),
-
- },
- TestNpmResolutionPackage {
- pkg_id: "package-a@1.0.0_package-peer-a@4.0.0__package-peer-b@5.4.1".to_string(),
- copy_index: 0,
- dependencies: BTreeMap::from([
- (
- "package-b".to_string(),
- "package-b@2.0.0_package-peer-a@4.0.0__package-peer-b@5.4.1_package-peer-c@6.2.0".to_string(),
- ),
- (
- "package-c".to_string(),
- "package-c@3.0.0_package-peer-a@4.0.0__package-peer-b@5.4.1".to_string(),
- ),
- (
- "package-d".to_string(),
- "package-d@3.5.0".to_string(),
- ),
- (
- "package-peer-a".to_string(),
- "package-peer-a@4.0.0_package-peer-b@5.4.1".to_string(),
- ),
- ]),
-
- },
- TestNpmResolutionPackage {
- pkg_id: "package-b@2.0.0_package-peer-a@4.0.0__package-peer-b@5.4.1_package-peer-c@6.2.0".to_string(),
- copy_index: 0,
- dependencies: BTreeMap::from([
- (
- "package-peer-a".to_string(),
- "package-peer-a@4.0.0_package-peer-b@5.4.1".to_string(),
- ),
- (
- "package-peer-c".to_string(),
- "package-peer-c@6.2.0".to_string(),
- )
- ])
- },
- TestNpmResolutionPackage {
- pkg_id: "package-c@3.0.0_package-peer-a@4.0.0__package-peer-b@5.4.1".to_string(),
- copy_index: 0,
- dependencies: BTreeMap::from([(
- "package-peer-a".to_string(),
- "package-peer-a@4.0.0_package-peer-b@5.4.1".to_string(),
- )])
- },
- TestNpmResolutionPackage {
- pkg_id: "package-d@3.5.0".to_string(),
- copy_index: 0,
- dependencies: BTreeMap::from([]),
-
- },
- TestNpmResolutionPackage {
- pkg_id: "package-e@3.6.0".to_string(),
- copy_index: 0,
- dependencies: BTreeMap::from([]),
-
- },
- TestNpmResolutionPackage {
- pkg_id: "package-peer-a@4.0.0_package-peer-b@5.4.1".to_string(),
- copy_index: 0,
- dependencies: BTreeMap::from([(
- "package-peer-b".to_string(),
- "package-peer-b@5.4.1".to_string(),
- )])
- },
- TestNpmResolutionPackage {
- pkg_id: "package-peer-b@5.4.1".to_string(),
- copy_index: 0,
- dependencies: Default::default(),
- },
- TestNpmResolutionPackage {
- pkg_id: "package-peer-c@6.2.0".to_string(),
- copy_index: 0,
- dependencies: Default::default(),
- },
- ]
- );
- assert_eq!(
- package_reqs,
- vec![
- ("package-0@1.1.1".to_string(), "package-0@1.1.1".to_string()),
- ("package-e@3".to_string(), "package-e@3.6.0".to_string()),
- ]
- );
- }
-
- #[tokio::test]
- async fn resolve_peer_deps_circular() {
- let api = TestNpmRegistryApiInner::default();
- api.ensure_package_version("package-a", "1.0.0");
- api.ensure_package_version("package-b", "2.0.0");
- api.add_dependency(("package-a", "1.0.0"), ("package-b", "*"));
- api.add_peer_dependency(("package-b", "2.0.0"), ("package-a", "1"));
-
- let (packages, package_reqs) =
- run_resolver_and_get_output(api, vec!["npm:package-a@1.0"]).await;
- assert_eq!(
- packages,
- vec![
- TestNpmResolutionPackage {
- pkg_id: "package-a@1.0.0".to_string(),
- copy_index: 0,
- dependencies: BTreeMap::from([(
- "package-b".to_string(),
- "package-b@2.0.0_package-a@1.0.0".to_string(),
- )]),
- },
- TestNpmResolutionPackage {
- pkg_id: "package-b@2.0.0_package-a@1.0.0".to_string(),
- copy_index: 0,
- dependencies: BTreeMap::from([(
- "package-a".to_string(),
- "package-a@1.0.0".to_string(),
- )]),
- },
- ]
- );
- assert_eq!(
- package_reqs,
- vec![("package-a@1.0".to_string(), "package-a@1.0.0".to_string())]
- );
- }
-
- #[tokio::test]
- async fn resolve_peer_deps_multiple_copies() {
- // repeat this a few times to have a higher probability of surfacing indeterminism
- for _ in 0..3 {
- let api = TestNpmRegistryApiInner::default();
- api.ensure_package_version("package-a", "1.0.0");
- api.ensure_package_version("package-b", "2.0.0");
- api.ensure_package_version("package-dep", "3.0.0");
- api.ensure_package_version("package-peer", "4.0.0");
- api.ensure_package_version("package-peer", "5.0.0");
- api.add_dependency(("package-a", "1.0.0"), ("package-dep", "*"));
- api.add_dependency(("package-a", "1.0.0"), ("package-peer", "4"));
- api.add_dependency(("package-b", "2.0.0"), ("package-dep", "*"));
- api.add_dependency(("package-b", "2.0.0"), ("package-peer", "5"));
- api.add_peer_dependency(("package-dep", "3.0.0"), ("package-peer", "*"));
-
- let (packages, package_reqs) = run_resolver_and_get_output(
- api,
- vec!["npm:package-a@1", "npm:package-b@2"],
- )
- .await;
- assert_eq!(
- packages,
- vec![
- TestNpmResolutionPackage {
- pkg_id: "package-a@1.0.0_package-peer@4.0.0".to_string(),
- copy_index: 0,
- dependencies: BTreeMap::from([
- (
- "package-dep".to_string(),
- "package-dep@3.0.0_package-peer@4.0.0".to_string(),
- ),
- ("package-peer".to_string(), "package-peer@4.0.0".to_string(),),
- ]),
- },
- TestNpmResolutionPackage {
- pkg_id: "package-b@2.0.0_package-peer@5.0.0".to_string(),
- copy_index: 0,
- dependencies: BTreeMap::from([
- (
- "package-dep".to_string(),
- "package-dep@3.0.0_package-peer@5.0.0".to_string(),
- ),
- ("package-peer".to_string(), "package-peer@5.0.0".to_string(),),
- ]),
- },
- TestNpmResolutionPackage {
- pkg_id: "package-dep@3.0.0_package-peer@4.0.0".to_string(),
- copy_index: 0,
- dependencies: BTreeMap::from([(
- "package-peer".to_string(),
- "package-peer@4.0.0".to_string(),
- )]),
- },
- TestNpmResolutionPackage {
- pkg_id: "package-dep@3.0.0_package-peer@5.0.0".to_string(),
- copy_index: 1,
- dependencies: BTreeMap::from([(
- "package-peer".to_string(),
- "package-peer@5.0.0".to_string(),
- )]),
- },
- TestNpmResolutionPackage {
- pkg_id: "package-peer@4.0.0".to_string(),
- copy_index: 0,
- dependencies: Default::default(),
- },
- TestNpmResolutionPackage {
- pkg_id: "package-peer@5.0.0".to_string(),
- copy_index: 0,
- dependencies: Default::default(),
- },
- ]
- );
- assert_eq!(
- package_reqs,
- vec![
- (
- "package-a@1".to_string(),
- "package-a@1.0.0_package-peer@4.0.0".to_string()
- ),
- (
- "package-b@2".to_string(),
- "package-b@2.0.0_package-peer@5.0.0".to_string()
- )
- ]
- );
- }
- }
-
- #[tokio::test]
- async fn resolve_dep_with_peer_deps_dep_then_peer() {
- let api = TestNpmRegistryApiInner::default();
- api.ensure_package_version("package-a", "1.0.0");
- api.ensure_package_version("package-b", "1.0.0");
- api.ensure_package_version("package-c", "1.0.0");
- api.ensure_package_version("package-peer", "1.0.0");
- api.add_peer_dependency(("package-b", "1.0.0"), ("package-peer", "1"));
- api.add_dependency(("package-a", "1.0.0"), ("package-c", "1"));
- api.add_dependency(("package-a", "1.0.0"), ("package-peer", "1"));
- api.add_peer_dependency(("package-c", "1.0.0"), ("package-b", "1"));
-
- let (packages, package_reqs) = run_resolver_and_get_output(
- api,
- vec!["npm:package-a@1.0", "npm:package-b@1.0"],
- )
- .await;
- assert_eq!(
- packages,
- vec![
- TestNpmResolutionPackage {
- pkg_id: "package-a@1.0.0_package-b@1.0.0__package-peer@1.0.0"
- .to_string(),
- copy_index: 0,
- dependencies: BTreeMap::from([
- (
- "package-c".to_string(),
- "package-c@1.0.0_package-b@1.0.0__package-peer@1.0.0".to_string(),
- ),
- ("package-peer".to_string(), "package-peer@1.0.0".to_string(),)
- ]),
- },
- TestNpmResolutionPackage {
- pkg_id: "package-b@1.0.0_package-peer@1.0.0".to_string(),
- copy_index: 0,
- dependencies: BTreeMap::from([(
- "package-peer".to_string(),
- "package-peer@1.0.0".to_string(),
- )]),
- },
- TestNpmResolutionPackage {
- pkg_id: "package-c@1.0.0_package-b@1.0.0__package-peer@1.0.0"
- .to_string(),
- copy_index: 0,
- dependencies: BTreeMap::from([(
- "package-b".to_string(),
- "package-b@1.0.0_package-peer@1.0.0".to_string(),
- )]),
- },
- TestNpmResolutionPackage {
- pkg_id: "package-peer@1.0.0".to_string(),
- copy_index: 0,
- dependencies: BTreeMap::from([]),
- },
- ]
- );
- assert_eq!(
- package_reqs,
- vec![
- (
- "package-a@1.0".to_string(),
- "package-a@1.0.0_package-b@1.0.0__package-peer@1.0.0".to_string()
- ),
- (
- "package-b@1.0".to_string(),
- "package-b@1.0.0_package-peer@1.0.0".to_string()
- )
- ]
- );
- }
-
- #[tokio::test]
- async fn resolve_dep_with_peer_deps_then_other_dep_with_different_peer() {
- let api = TestNpmRegistryApiInner::default();
- api.ensure_package_version("package-a", "1.0.0");
- api.ensure_package_version("package-b", "1.0.0");
- api.ensure_package_version("package-c", "1.0.0");
- api.ensure_package_version("package-peer", "1.1.0");
- api.ensure_package_version("package-peer", "1.2.0");
- api.add_peer_dependency(("package-a", "1.0.0"), ("package-peer", "*")); // should select 1.2.0, then 1.1.0
- api.add_dependency(("package-b", "1.0.0"), ("package-c", "1"));
- api.add_dependency(("package-b", "1.0.0"), ("package-peer", "=1.1.0"));
- api.add_dependency(("package-c", "1.0.0"), ("package-a", "1"));
-
- let (packages, package_reqs) = run_resolver_and_get_output(
- api,
- vec!["npm:package-a@1.0", "npm:package-b@1.0"],
- )
- .await;
- assert_eq!(
- packages,
- vec![
- TestNpmResolutionPackage {
- pkg_id: "package-a@1.0.0_package-peer@1.1.0".to_string(),
- copy_index: 1,
- dependencies: BTreeMap::from([(
- "package-peer".to_string(),
- "package-peer@1.1.0".to_string(),
- )]),
- },
- TestNpmResolutionPackage {
- pkg_id: "package-a@1.0.0_package-peer@1.2.0".to_string(),
- copy_index: 0,
- dependencies: BTreeMap::from([(
- "package-peer".to_string(),
- "package-peer@1.2.0".to_string(),
- )]),
- },
- TestNpmResolutionPackage {
- pkg_id: "package-b@1.0.0_package-peer@1.1.0".to_string(),
- copy_index: 0,
- dependencies: BTreeMap::from([
- (
- "package-c".to_string(),
- "package-c@1.0.0_package-peer@1.1.0".to_string(),
- ),
- ("package-peer".to_string(), "package-peer@1.1.0".to_string(),)
- ]),
- },
- TestNpmResolutionPackage {
- pkg_id: "package-c@1.0.0_package-peer@1.1.0".to_string(),
- copy_index: 0,
- dependencies: BTreeMap::from([(
- "package-a".to_string(),
- "package-a@1.0.0_package-peer@1.1.0".to_string(),
- )]),
- },
- TestNpmResolutionPackage {
- pkg_id: "package-peer@1.1.0".to_string(),
- copy_index: 0,
- dependencies: BTreeMap::from([]),
- },
- TestNpmResolutionPackage {
- pkg_id: "package-peer@1.2.0".to_string(),
- copy_index: 0,
- dependencies: BTreeMap::from([]),
- },
- ]
- );
- assert_eq!(
- package_reqs,
- vec![
- (
- "package-a@1.0".to_string(),
- "package-a@1.0.0_package-peer@1.2.0".to_string()
- ),
- (
- "package-b@1.0".to_string(),
- "package-b@1.0.0_package-peer@1.1.0".to_string()
- )
- ]
- );
- }
-
- #[tokio::test]
- async fn resolve_dep_and_peer_dist_tag() {
- let api = TestNpmRegistryApiInner::default();
- api.ensure_package_version("package-a", "1.0.0");
- api.ensure_package_version("package-b", "2.0.0");
- api.ensure_package_version("package-b", "3.0.0");
- api.ensure_package_version("package-c", "1.0.0");
- api.ensure_package_version("package-d", "1.0.0");
- api.ensure_package_version("package-e", "1.0.0");
- api.add_dependency(("package-a", "1.0.0"), ("package-b", "some-tag"));
- api.add_dependency(("package-a", "1.0.0"), ("package-d", "1.0.0"));
- api.add_dependency(("package-a", "1.0.0"), ("package-c", "1.0.0"));
- api.add_dependency(("package-a", "1.0.0"), ("package-e", "1.0.0"));
- api.add_dependency(("package-e", "1.0.0"), ("package-b", "some-tag"));
- api.add_peer_dependency(("package-c", "1.0.0"), ("package-d", "other-tag"));
- api.add_dist_tag("package-b", "some-tag", "2.0.0");
- api.add_dist_tag("package-d", "other-tag", "1.0.0");
-
- let (packages, package_reqs) =
- run_resolver_and_get_output(api, vec!["npm:package-a@1.0"]).await;
- assert_eq!(
- packages,
- vec![
- TestNpmResolutionPackage {
- pkg_id: "package-a@1.0.0_package-d@1.0.0".to_string(),
- copy_index: 0,
- dependencies: BTreeMap::from([
- ("package-b".to_string(), "package-b@2.0.0".to_string(),),
- (
- "package-c".to_string(),
- "package-c@1.0.0_package-d@1.0.0".to_string(),
- ),
- ("package-d".to_string(), "package-d@1.0.0".to_string(),),
- ("package-e".to_string(), "package-e@1.0.0".to_string(),),
- ]),
- },
- TestNpmResolutionPackage {
- pkg_id: "package-b@2.0.0".to_string(),
- copy_index: 0,
- dependencies: Default::default(),
- },
- TestNpmResolutionPackage {
- pkg_id: "package-c@1.0.0_package-d@1.0.0".to_string(),
- copy_index: 0,
- dependencies: BTreeMap::from([(
- "package-d".to_string(),
- "package-d@1.0.0".to_string(),
- ),]),
- },
- TestNpmResolutionPackage {
- pkg_id: "package-d@1.0.0".to_string(),
- copy_index: 0,
- dependencies: Default::default(),
- },
- TestNpmResolutionPackage {
- pkg_id: "package-e@1.0.0".to_string(),
- copy_index: 0,
- dependencies: BTreeMap::from([(
- "package-b".to_string(),
- "package-b@2.0.0".to_string(),
- )]),
- },
- ]
- );
- assert_eq!(
- package_reqs,
- vec![(
- "package-a@1.0".to_string(),
- "package-a@1.0.0_package-d@1.0.0".to_string()
- ),]
- );
- }
-
- #[tokio::test]
- async fn package_has_self_as_dependency() {
- let api = TestNpmRegistryApiInner::default();
- api.ensure_package_version("package-a", "1.0.0");
- api.add_dependency(("package-a", "1.0.0"), ("package-a", "1"));
-
- let (packages, package_reqs) =
- run_resolver_and_get_output(api, vec!["npm:package-a@1.0"]).await;
- assert_eq!(
- packages,
- vec![TestNpmResolutionPackage {
- pkg_id: "package-a@1.0.0".to_string(),
- copy_index: 0,
- // in this case, we just ignore that the package did this
- dependencies: Default::default(),
- }]
- );
- assert_eq!(
- package_reqs,
- vec![("package-a@1.0".to_string(), "package-a@1.0.0".to_string())]
- );
- }
-
- #[tokio::test]
- async fn package_has_self_but_different_version_as_dependency() {
- let api = TestNpmRegistryApiInner::default();
- api.ensure_package_version("package-a", "1.0.0");
- api.ensure_package_version("package-a", "0.5.0");
- api.add_dependency(("package-a", "1.0.0"), ("package-a", "^0.5"));
-
- let (packages, package_reqs) =
- run_resolver_and_get_output(api, vec!["npm:package-a@1.0"]).await;
- assert_eq!(
- packages,
- vec![
- TestNpmResolutionPackage {
- pkg_id: "package-a@0.5.0".to_string(),
- copy_index: 0,
- dependencies: Default::default(),
- },
- TestNpmResolutionPackage {
- pkg_id: "package-a@1.0.0".to_string(),
- copy_index: 0,
- dependencies: BTreeMap::from([(
- "package-a".to_string(),
- "package-a@0.5.0".to_string(),
- )]),
- },
- ]
- );
- assert_eq!(
- package_reqs,
- vec![("package-a@1.0".to_string(), "package-a@1.0.0".to_string())]
- );
- }
-
- #[tokio::test]
- async fn grand_child_package_has_self_as_peer_dependency_root() {
- let api = TestNpmRegistryApiInner::default();
- api.ensure_package_version("package-a", "1.0.0");
- api.ensure_package_version("package-b", "2.0.0");
- api.add_dependency(("package-a", "1.0.0"), ("package-b", "2"));
- api.add_peer_dependency(("package-b", "2.0.0"), ("package-a", "*"));
-
- let (packages, package_reqs) =
- run_resolver_and_get_output(api, vec!["npm:package-a@1.0"]).await;
- assert_eq!(
- packages,
- vec![
- TestNpmResolutionPackage {
- pkg_id: "package-a@1.0.0".to_string(),
- copy_index: 0,
- dependencies: BTreeMap::from([(
- "package-b".to_string(),
- "package-b@2.0.0_package-a@1.0.0".to_string(),
- )]),
- },
- TestNpmResolutionPackage {
- pkg_id: "package-b@2.0.0_package-a@1.0.0".to_string(),
- copy_index: 0,
- dependencies: BTreeMap::from([(
- "package-a".to_string(),
- "package-a@1.0.0".to_string(),
- )]),
- }
- ]
- );
- assert_eq!(
- package_reqs,
- vec![("package-a@1.0".to_string(), "package-a@1.0.0".to_string())]
- );
- }
-
- #[tokio::test]
- async fn grand_child_package_has_self_as_peer_dependency_under_root() {
- let api = TestNpmRegistryApiInner::default();
- api.ensure_package_version("package-0", "1.0.0");
- api.ensure_package_version("package-a", "1.0.0");
- api.ensure_package_version("package-b", "2.0.0");
- api.add_dependency(("package-0", "1.0.0"), ("package-a", "*"));
- api.add_dependency(("package-a", "1.0.0"), ("package-b", "2"));
- api.add_peer_dependency(("package-b", "2.0.0"), ("package-a", "*"));
-
- let (packages, package_reqs) =
- run_resolver_and_get_output(api, vec!["npm:package-0@1.0"]).await;
- assert_eq!(
- packages,
- vec![
- TestNpmResolutionPackage {
- pkg_id: "package-0@1.0.0".to_string(),
- copy_index: 0,
- dependencies: BTreeMap::from([(
- "package-a".to_string(),
- "package-a@1.0.0".to_string(),
- )]),
- },
- TestNpmResolutionPackage {
- pkg_id: "package-a@1.0.0".to_string(),
- copy_index: 0,
- dependencies: BTreeMap::from([(
- "package-b".to_string(),
- "package-b@2.0.0_package-a@1.0.0".to_string(),
- )]),
- },
- TestNpmResolutionPackage {
- pkg_id: "package-b@2.0.0_package-a@1.0.0".to_string(),
- copy_index: 0,
- dependencies: BTreeMap::from([(
- "package-a".to_string(),
- "package-a@1.0.0".to_string(),
- )]),
- }
- ]
- );
- assert_eq!(
- package_reqs,
- vec![("package-0@1.0".to_string(), "package-0@1.0.0".to_string())]
- );
- }
-
- #[tokio::test]
- async fn resolve_peer_deps_in_ancestor_root() {
- let api = TestNpmRegistryApiInner::default();
- api.ensure_package_version("package-a", "1.0.0");
- api.ensure_package_version("package-b", "1.0.0");
- api.ensure_package_version("package-c", "1.0.0");
- api.add_dependency(("package-a", "1.0.0"), ("package-b", "1"));
- api.add_dependency(("package-b", "1.0.0"), ("package-c", "1"));
- api.add_peer_dependency(("package-c", "1.0.0"), ("package-a", "1"));
-
- let (packages, package_reqs) =
- run_resolver_and_get_output(api, vec!["npm:package-a@1.0.0"]).await;
- assert_eq!(
- packages,
- vec![
- TestNpmResolutionPackage {
- pkg_id: "package-a@1.0.0".to_string(),
- copy_index: 0,
- dependencies: BTreeMap::from([(
- "package-b".to_string(),
- "package-b@1.0.0_package-a@1.0.0".to_string(),
- )]),
- },
- TestNpmResolutionPackage {
- pkg_id: "package-b@1.0.0_package-a@1.0.0".to_string(),
- copy_index: 0,
- dependencies: BTreeMap::from([(
- "package-c".to_string(),
- "package-c@1.0.0_package-a@1.0.0".to_string(),
- )]),
- },
- TestNpmResolutionPackage {
- pkg_id: "package-c@1.0.0_package-a@1.0.0".to_string(),
- copy_index: 0,
- dependencies: BTreeMap::from([(
- "package-a".to_string(),
- "package-a@1.0.0".to_string(),
- )]),
- },
- ]
- );
- assert_eq!(
- package_reqs,
- vec![("package-a@1.0.0".to_string(), "package-a@1.0.0".to_string())]
- );
- }
-
- #[tokio::test]
- async fn resolve_peer_deps_in_ancestor_non_root() {
- let api = TestNpmRegistryApiInner::default();
- api.ensure_package_version("package-a", "1.0.0");
- api.ensure_package_version("package-b", "1.0.0");
- api.ensure_package_version("package-c", "1.0.0");
- api.add_dependency(("package-a", "1.0.0"), ("package-b", "1"));
- api.add_dependency(("package-b", "1.0.0"), ("package-c", "1"));
- api.add_peer_dependency(("package-c", "1.0.0"), ("package-b", "1"));
-
- let (packages, package_reqs) =
- run_resolver_and_get_output(api, vec!["npm:package-a@1.0.0"]).await;
- assert_eq!(
- packages,
- vec![
- TestNpmResolutionPackage {
- pkg_id: "package-a@1.0.0".to_string(),
- copy_index: 0,
- dependencies: BTreeMap::from([(
- "package-b".to_string(),
- "package-b@1.0.0".to_string(),
- ),]),
- },
- TestNpmResolutionPackage {
- pkg_id: "package-b@1.0.0".to_string(),
- copy_index: 0,
- dependencies: BTreeMap::from([(
- "package-c".to_string(),
- "package-c@1.0.0_package-b@1.0.0".to_string(),
- )]),
- },
- TestNpmResolutionPackage {
- pkg_id: "package-c@1.0.0_package-b@1.0.0".to_string(),
- copy_index: 0,
- dependencies: BTreeMap::from([(
- "package-b".to_string(),
- "package-b@1.0.0".to_string(),
- )]),
- },
- ]
- );
- assert_eq!(
- package_reqs,
- vec![("package-a@1.0.0".to_string(), "package-a@1.0.0".to_string())]
- );
- }
-
- #[tokio::test]
- async fn nested_deps_same_peer_dep_ancestor() {
- let api = TestNpmRegistryApiInner::default();
- api.ensure_package_version("package-0", "1.0.0");
- api.ensure_package_version("package-1", "1.0.0");
- api.ensure_package_version("package-a", "1.0.0");
- api.ensure_package_version("package-b", "1.0.0");
- api.ensure_package_version("package-c", "1.0.0");
- api.ensure_package_version("package-d", "1.0.0");
- api.add_dependency(("package-0", "1.0.0"), ("package-a", "1"));
- api.add_dependency(("package-0", "1.0.0"), ("package-1", "1"));
- api.add_dependency(("package-1", "1.0.0"), ("package-a", "1"));
- api.add_dependency(("package-a", "1.0.0"), ("package-b", "1"));
- api.add_dependency(("package-b", "1.0.0"), ("package-c", "1"));
- api.add_dependency(("package-c", "1.0.0"), ("package-d", "1"));
- api.add_peer_dependency(("package-b", "1.0.0"), ("package-a", "*"));
- api.add_peer_dependency(("package-c", "1.0.0"), ("package-a", "*"));
- api.add_peer_dependency(("package-d", "1.0.0"), ("package-a", "*"));
- api.add_peer_dependency(("package-b", "1.0.0"), ("package-0", "*"));
- api.add_peer_dependency(("package-c", "1.0.0"), ("package-0", "*"));
- api.add_peer_dependency(("package-d", "1.0.0"), ("package-0", "*"));
-
- let (packages, package_reqs) =
- run_resolver_and_get_output(api, vec!["npm:package-0@1.0"]).await;
- assert_eq!(
- packages,
- vec![
- TestNpmResolutionPackage {
- pkg_id: "package-0@1.0.0".to_string(),
- copy_index: 0,
- dependencies: BTreeMap::from([(
- "package-a".to_string(),
- "package-a@1.0.0_package-0@1.0.0".to_string(),
- ), (
- "package-1".to_string(),
- "package-1@1.0.0_package-0@1.0.0".to_string(),
- )]),
- },
- TestNpmResolutionPackage {
- pkg_id: "package-1@1.0.0_package-0@1.0.0".to_string(),
- copy_index: 0,
- dependencies: BTreeMap::from([(
- "package-a".to_string(),
- "package-a@1.0.0_package-0@1.0.0".to_string(),
- )]),
- },
- TestNpmResolutionPackage {
- pkg_id: "package-a@1.0.0_package-0@1.0.0".to_string(),
- copy_index: 0,
- dependencies: BTreeMap::from([(
- "package-b".to_string(),
- "package-b@1.0.0_package-0@1.0.0_package-a@1.0.0__package-0@1.0.0".to_string(),
- )]),
- },
- TestNpmResolutionPackage {
- pkg_id: "package-b@1.0.0_package-0@1.0.0_package-a@1.0.0__package-0@1.0.0".to_string(),
- copy_index: 0,
- dependencies: BTreeMap::from([
- (
- "package-0".to_string(),
- "package-0@1.0.0".to_string(),
- ),
- (
- "package-a".to_string(),
- "package-a@1.0.0_package-0@1.0.0".to_string(),
- ),
- (
- "package-c".to_string(),
- "package-c@1.0.0_package-0@1.0.0_package-a@1.0.0__package-0@1.0.0".to_string(),
- )
- ]),
-
- },
- TestNpmResolutionPackage {
- pkg_id: "package-c@1.0.0_package-0@1.0.0_package-a@1.0.0__package-0@1.0.0".to_string(),
- copy_index: 0,
- dependencies: BTreeMap::from([
- (
- "package-0".to_string(),
- "package-0@1.0.0".to_string(),
- ),
- (
- "package-a".to_string(),
- "package-a@1.0.0_package-0@1.0.0".to_string(),
- ),
- (
- "package-d".to_string(),
- "package-d@1.0.0_package-0@1.0.0_package-a@1.0.0__package-0@1.0.0".to_string(),
- )
- ]),
-
- },
- TestNpmResolutionPackage {
- pkg_id: "package-d@1.0.0_package-0@1.0.0_package-a@1.0.0__package-0@1.0.0".to_string(),
- copy_index: 0,
- dependencies: BTreeMap::from([
- (
- "package-0".to_string(),
- "package-0@1.0.0".to_string(),
- ),
- (
- "package-a".to_string(),
- "package-a@1.0.0_package-0@1.0.0".to_string(),
- )
- ]),
-
- }
- ]
- );
- assert_eq!(
- package_reqs,
- vec![("package-0@1.0".to_string(), "package-0@1.0.0".to_string())]
- );
- }
-
- #[tokio::test]
- async fn peer_dep_resolved_then_resolved_deeper() {
- let api = TestNpmRegistryApiInner::default();
- api.ensure_package_version("package-0", "1.0.0");
- api.ensure_package_version("package-1", "1.0.0");
- api.ensure_package_version("package-a", "1.0.0");
- api.ensure_package_version("package-b", "1.0.0");
- api.ensure_package_version("package-peer", "1.0.0");
- api.add_dependency(("package-0", "1.0.0"), ("package-a", "1"));
- api.add_dependency(("package-0", "1.0.0"), ("package-1", "1"));
- api.add_dependency(("package-1", "1.0.0"), ("package-a", "1"));
- api.add_dependency(("package-a", "1.0.0"), ("package-b", "1"));
- api.add_peer_dependency(("package-b", "1.0.0"), ("package-peer", "*"));
-
- let (packages, package_reqs) = run_resolver_and_get_output(
- api,
- vec!["npm:package-0@1.0", "npm:package-peer@1.0"],
- )
- .await;
- assert_eq!(
- packages,
- vec![
- TestNpmResolutionPackage {
- pkg_id: "package-0@1.0.0_package-peer@1.0.0".to_string(),
- copy_index: 0,
- dependencies: BTreeMap::from([
- (
- "package-1".to_string(),
- "package-1@1.0.0_package-peer@1.0.0".to_string(),
- ),
- (
- "package-a".to_string(),
- "package-a@1.0.0_package-peer@1.0.0".to_string(),
- )
- ]),
- },
- TestNpmResolutionPackage {
- pkg_id: "package-1@1.0.0_package-peer@1.0.0".to_string(),
- copy_index: 0,
- dependencies: BTreeMap::from([(
- "package-a".to_string(),
- "package-a@1.0.0_package-peer@1.0.0".to_string(),
- )]),
- },
- TestNpmResolutionPackage {
- pkg_id: "package-a@1.0.0_package-peer@1.0.0".to_string(),
- copy_index: 0,
- dependencies: BTreeMap::from([(
- "package-b".to_string(),
- "package-b@1.0.0_package-peer@1.0.0".to_string(),
- )]),
- },
- TestNpmResolutionPackage {
- pkg_id: "package-b@1.0.0_package-peer@1.0.0".to_string(),
- copy_index: 0,
- dependencies: BTreeMap::from([(
- "package-peer".to_string(),
- "package-peer@1.0.0".to_string(),
- )]),
- },
- TestNpmResolutionPackage {
- pkg_id: "package-peer@1.0.0".to_string(),
- copy_index: 0,
- dependencies: Default::default(),
- }
- ]
- );
- assert_eq!(
- package_reqs,
- vec![
- (
- "package-0@1.0".to_string(),
- "package-0@1.0.0_package-peer@1.0.0".to_string()
- ),
- (
- "package-peer@1.0".to_string(),
- "package-peer@1.0.0".to_string()
- )
- ]
- );
- }
-
- #[tokio::test]
- async fn resolve_dep_with_peer_deps_circular_1() {
- // a -> b -> c -> d -> c where c has a peer dependency on b
- let api = TestNpmRegistryApiInner::default();
- api.ensure_package_version("package-a", "1.0.0");
- api.ensure_package_version("package-b", "1.0.0");
- api.ensure_package_version("package-c", "1.0.0");
- api.ensure_package_version("package-d", "1.0.0");
- api.add_dependency(("package-a", "1.0.0"), ("package-b", "1"));
- api.add_dependency(("package-b", "1.0.0"), ("package-c", "1"));
- api.add_dependency(("package-c", "1.0.0"), ("package-d", "1"));
- api.add_dependency(("package-d", "1.0.0"), ("package-c", "1"));
- api.add_peer_dependency(("package-c", "1.0.0"), ("package-b", "1"));
-
- let (packages, package_reqs) =
- run_resolver_and_get_output(api, vec!["npm:package-a@1.0.0"]).await;
- assert_eq!(
- packages,
- vec![
- TestNpmResolutionPackage {
- pkg_id: "package-a@1.0.0".to_string(),
- copy_index: 0,
- dependencies: BTreeMap::from([(
- "package-b".to_string(),
- "package-b@1.0.0".to_string(),
- ),]),
- },
- TestNpmResolutionPackage {
- pkg_id: "package-b@1.0.0".to_string(),
- copy_index: 0,
- dependencies: BTreeMap::from([(
- "package-c".to_string(),
- "package-c@1.0.0_package-b@1.0.0".to_string(),
- )]),
- },
- TestNpmResolutionPackage {
- pkg_id: "package-c@1.0.0_package-b@1.0.0".to_string(),
- copy_index: 0,
- dependencies: BTreeMap::from([
- ("package-b".to_string(), "package-b@1.0.0".to_string(),),
- (
- "package-d".to_string(),
- "package-d@1.0.0_package-b@1.0.0".to_string(),
- )
- ]),
- },
- TestNpmResolutionPackage {
- pkg_id: "package-d@1.0.0_package-b@1.0.0".to_string(),
- copy_index: 0,
- dependencies: BTreeMap::from([(
- "package-c".to_string(),
- "package-c@1.0.0_package-b@1.0.0".to_string(),
- )]),
- },
- ]
- );
- assert_eq!(
- package_reqs,
- vec![("package-a@1.0.0".to_string(), "package-a@1.0.0".to_string())]
- );
- }
-
- #[tokio::test]
- async fn resolve_dep_with_peer_deps_circular_2() {
- // a -> b -> c -> d -> c where c has a peer dependency on b
- // -> e -> f -> d -> c where f has a peer dep on a
- let api = TestNpmRegistryApiInner::default();
- api.ensure_package_version("package-a", "1.0.0");
- api.ensure_package_version("package-b", "1.0.0");
- api.ensure_package_version("package-c", "1.0.0");
- api.ensure_package_version("package-d", "1.0.0");
- api.ensure_package_version("package-e", "1.0.0");
- api.ensure_package_version("package-f", "1.0.0");
- api.add_dependency(("package-a", "1.0.0"), ("package-b", "1"));
- api.add_dependency(("package-b", "1.0.0"), ("package-c", "1"));
- api.add_dependency(("package-c", "1.0.0"), ("package-d", "1"));
- api.add_dependency(("package-c", "1.0.0"), ("package-e", "1"));
- api.add_dependency(("package-d", "1.0.0"), ("package-c", "1"));
- api.add_dependency(("package-e", "1.0.0"), ("package-f", "1"));
- api.add_dependency(("package-f", "1.0.0"), ("package-d", "1"));
- api.add_peer_dependency(("package-f", "1.0.0"), ("package-a", "1"));
- api.add_peer_dependency(("package-c", "1.0.0"), ("package-b", "1"));
-
- let (packages, package_reqs) =
- run_resolver_and_get_output(api, vec!["npm:package-a@1.0.0"]).await;
- assert_eq!(
- packages,
- vec![
- TestNpmResolutionPackage {
- pkg_id: "package-a@1.0.0".to_string(),
- copy_index: 0,
- dependencies: BTreeMap::from([(
- "package-b".to_string(),
- "package-b@1.0.0_package-a@1.0.0".to_string(),
- )]),
- },
- TestNpmResolutionPackage {
- pkg_id: "package-b@1.0.0_package-a@1.0.0".to_string(),
- copy_index: 0,
- dependencies: BTreeMap::from([(
- "package-c".to_string(),
- "package-c@1.0.0_package-b@1.0.0__package-a@1.0.0_package-a@1.0.0".to_string(),
- )]),
- },
- TestNpmResolutionPackage {
- pkg_id: "package-c@1.0.0_package-b@1.0.0__package-a@1.0.0_package-a@1.0.0".to_string(),
- copy_index: 0,
- dependencies: BTreeMap::from([
- (
- "package-b".to_string(),
- "package-b@1.0.0_package-a@1.0.0".to_string(),
- ),
- (
- "package-d".to_string(),
- "package-d@1.0.0_package-b@1.0.0__package-a@1.0.0_package-a@1.0.0".to_string(),
- ),
- (
- "package-e".to_string(),
- "package-e@1.0.0_package-a@1.0.0_package-b@1.0.0__package-a@1.0.0".to_string()
- )
- ]),
-
- },
- TestNpmResolutionPackage {
- pkg_id: "package-d@1.0.0_package-b@1.0.0__package-a@1.0.0_package-a@1.0.0".to_string(),
- copy_index: 0,
- dependencies: BTreeMap::from([(
- "package-c".to_string(),
- "package-c@1.0.0_package-b@1.0.0__package-a@1.0.0_package-a@1.0.0".to_string(),
- )]),
- },
- TestNpmResolutionPackage {
- pkg_id: "package-e@1.0.0_package-a@1.0.0_package-b@1.0.0__package-a@1.0.0".to_string(),
- copy_index: 0,
- dependencies: BTreeMap::from([(
- "package-f".to_string(),
- "package-f@1.0.0_package-a@1.0.0_package-b@1.0.0__package-a@1.0.0".to_string(),
- )]),
- },
- TestNpmResolutionPackage {
- pkg_id: "package-f@1.0.0_package-a@1.0.0_package-b@1.0.0__package-a@1.0.0".to_string(),
- copy_index: 0,
- dependencies: BTreeMap::from([(
- "package-a".to_string(),
- "package-a@1.0.0".to_string(),
- ), (
- "package-d".to_string(),
- "package-d@1.0.0_package-b@1.0.0__package-a@1.0.0_package-a@1.0.0".to_string(),
- )]),
- },
- ]
- );
- assert_eq!(
- package_reqs,
- vec![("package-a@1.0.0".to_string(), "package-a@1.0.0".to_string())]
- );
- }
-
- #[tokio::test]
- async fn resolve_dep_with_peer_deps_circular_3() {
- // a -> b -> c -> d -> c (peer)
- // -> e -> a (peer)
- let api = TestNpmRegistryApiInner::default();
- api.ensure_package_version("package-a", "1.0.0");
- api.ensure_package_version("package-b", "1.0.0");
- api.ensure_package_version("package-c", "1.0.0");
- api.ensure_package_version("package-d", "1.0.0");
- api.ensure_package_version("package-e", "1.0.0");
- api.add_dependency(("package-a", "1.0.0"), ("package-b", "1"));
- api.add_dependency(("package-b", "1.0.0"), ("package-c", "1"));
- api.add_dependency(("package-c", "1.0.0"), ("package-d", "1"));
- api.add_dependency(("package-d", "1.0.0"), ("package-e", "1"));
- api.add_peer_dependency(("package-d", "1.0.0"), ("package-c", "1"));
- api.add_peer_dependency(("package-e", "1.0.0"), ("package-a", "1"));
-
- let (packages, package_reqs) =
- run_resolver_and_get_output(api, vec!["npm:package-a@1.0.0"]).await;
- assert_eq!(
- packages,
- vec![
- TestNpmResolutionPackage {
- pkg_id: "package-a@1.0.0".to_string(),
- copy_index: 0,
- dependencies: BTreeMap::from([(
- "package-b".to_string(),
- "package-b@1.0.0_package-a@1.0.0".to_string(),
- ),]),
- },
- TestNpmResolutionPackage {
- pkg_id: "package-b@1.0.0_package-a@1.0.0".to_string(),
- copy_index: 0,
- dependencies: BTreeMap::from([(
- "package-c".to_string(),
- "package-c@1.0.0_package-a@1.0.0".to_string(),
- )]),
- },
- TestNpmResolutionPackage {
- pkg_id: "package-c@1.0.0_package-a@1.0.0".to_string(),
- copy_index: 0,
- dependencies: BTreeMap::from([(
- "package-d".to_string(),
- "package-d@1.0.0_package-c@1.0.0__package-a@1.0.0_package-a@1.0.0"
- .to_string(),
- )]),
- },
- TestNpmResolutionPackage {
- pkg_id:
- "package-d@1.0.0_package-c@1.0.0__package-a@1.0.0_package-a@1.0.0"
- .to_string(),
- copy_index: 0,
- dependencies: BTreeMap::from([
- (
- "package-c".to_string(),
- "package-c@1.0.0_package-a@1.0.0".to_string(),
- ),
- (
- "package-e".to_string(),
- "package-e@1.0.0_package-a@1.0.0".to_string()
- ),
- ]),
- },
- TestNpmResolutionPackage {
- pkg_id: "package-e@1.0.0_package-a@1.0.0".to_string(),
- copy_index: 0,
- dependencies: BTreeMap::from([(
- "package-a".to_string(),
- "package-a@1.0.0".to_string()
- ),]),
- },
- ]
- );
- assert_eq!(
- package_reqs,
- vec![("package-a@1.0.0".to_string(), "package-a@1.0.0".to_string())]
- );
- }
-
- #[derive(Debug, PartialEq, Eq)]
- struct TestNpmResolutionPackage {
- pub pkg_id: String,
- pub copy_index: usize,
- pub dependencies: BTreeMap<String, String>,
- }
-
- async fn run_resolver_and_get_output(
- api: TestNpmRegistryApiInner,
- reqs: Vec<&str>,
- ) -> (Vec<TestNpmResolutionPackage>, Vec<(String, String)>) {
- let mut graph = Graph::default();
- let api = NpmRegistryApi::new_for_test(api);
- let mut resolver = GraphDependencyResolver::new(&mut graph, &api);
-
- for req in reqs {
- let req = NpmPackageReqReference::from_str(req).unwrap().req;
- resolver
- .add_package_req(&req, &api.package_info(&req.name).await.unwrap())
- .unwrap();
- }
-
- resolver.resolve_pending().await.unwrap();
- let snapshot = graph.into_snapshot(&api).await.unwrap();
-
- {
- let new_snapshot = Graph::from_snapshot(snapshot.clone())
- .unwrap()
- .into_snapshot(&api)
- .await
- .unwrap();
- assert_eq!(
- snapshot, new_snapshot,
- "recreated snapshot should be the same"
- );
- // create one again from the new snapshot
- let new_snapshot2 = Graph::from_snapshot(new_snapshot.clone())
- .unwrap()
- .into_snapshot(&api)
- .await
- .unwrap();
- assert_eq!(
- snapshot, new_snapshot2,
- "second recreated snapshot should be the same"
- );
- }
-
- let mut packages = snapshot.all_packages();
- packages.sort_by(|a, b| a.pkg_id.cmp(&b.pkg_id));
- let mut package_reqs = snapshot
- .package_reqs
- .into_iter()
- .map(|(a, b)| {
- (
- a.to_string(),
- snapshot.root_packages.get(&b).unwrap().as_serialized(),
- )
- })
- .collect::<Vec<_>>();
- package_reqs.sort_by(|a, b| a.0.to_string().cmp(&b.0.to_string()));
- let packages = packages
- .into_iter()
- .map(|pkg| TestNpmResolutionPackage {
- pkg_id: pkg.pkg_id.as_serialized(),
- copy_index: pkg.copy_index,
- dependencies: pkg
- .dependencies
- .into_iter()
- .map(|(key, value)| (key, value.as_serialized()))
- .collect(),
- })
- .collect();
-
- (packages, package_reqs)
- }
-}
diff --git a/cli/npm/resolution/mod.rs b/cli/npm/resolution/mod.rs
deleted file mode 100644
index 82dc1c62c..000000000
--- a/cli/npm/resolution/mod.rs
+++ /dev/null
@@ -1,636 +0,0 @@
-// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
-
-use std::cmp::Ordering;
-use std::collections::BTreeMap;
-use std::collections::HashMap;
-use std::collections::HashSet;
-use std::sync::Arc;
-
-use deno_core::anyhow::Context;
-use deno_core::error::AnyError;
-use deno_core::parking_lot::Mutex;
-use deno_core::parking_lot::RwLock;
-use deno_core::TaskQueue;
-use deno_graph::npm::NpmPackageNv;
-use deno_graph::npm::NpmPackageNvReference;
-use deno_graph::npm::NpmPackageReq;
-use deno_graph::npm::NpmPackageReqReference;
-use deno_graph::semver::Version;
-use log::debug;
-use serde::Deserialize;
-use serde::Serialize;
-use thiserror::Error;
-
-use crate::args::Lockfile;
-use crate::npm::resolution::common::LATEST_VERSION_REQ;
-
-use self::common::resolve_best_package_version_and_info;
-use self::graph::GraphDependencyResolver;
-use self::snapshot::NpmPackagesPartitioned;
-
-use super::cache::NpmPackageCacheFolderId;
-use super::registry::NpmPackageVersionDistInfo;
-use super::registry::NpmRegistryApi;
-
-mod common;
-mod graph;
-mod snapshot;
-
-use graph::Graph;
-pub use snapshot::NpmResolutionSnapshot;
-
-#[derive(Debug, Error)]
-#[error("Invalid npm package id '{text}'. {message}")]
-pub struct NpmPackageNodeIdDeserializationError {
- message: String,
- text: String,
-}
-
-/// A resolved unique identifier for an npm package. This contains
-/// the resolved name, version, and peer dependency resolution identifiers.
-#[derive(Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
-pub struct NpmPackageId {
- pub nv: NpmPackageNv,
- pub peer_dependencies: Vec<NpmPackageId>,
-}
-
-// Custom debug implementation for more concise test output
-impl std::fmt::Debug for NpmPackageId {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- write!(f, "{}", self.as_serialized())
- }
-}
-
-impl NpmPackageId {
- pub fn as_serialized(&self) -> String {
- self.as_serialized_with_level(0)
- }
-
- fn as_serialized_with_level(&self, level: usize) -> String {
- // WARNING: This should not change because it's used in the lockfile
- let mut result = format!(
- "{}@{}",
- if level == 0 {
- self.nv.name.to_string()
- } else {
- self.nv.name.replace('/', "+")
- },
- self.nv.version
- );
- for peer in &self.peer_dependencies {
- // unfortunately we can't do something like `_3` when
- // this gets deep because npm package names can start
- // with a number
- result.push_str(&"_".repeat(level + 1));
- result.push_str(&peer.as_serialized_with_level(level + 1));
- }
- result
- }
-
- pub fn from_serialized(
- id: &str,
- ) -> Result<Self, NpmPackageNodeIdDeserializationError> {
- use monch::*;
-
- fn parse_name(input: &str) -> ParseResult<&str> {
- if_not_empty(substring(move |input| {
- for (pos, c) in input.char_indices() {
- // first character might be a scope, so skip it
- if pos > 0 && c == '@' {
- return Ok((&input[pos..], ()));
- }
- }
- ParseError::backtrace()
- }))(input)
- }
-
- fn parse_version(input: &str) -> ParseResult<&str> {
- if_not_empty(substring(skip_while(|c| c != '_')))(input)
- }
-
- fn parse_name_and_version(input: &str) -> ParseResult<(String, Version)> {
- let (input, name) = parse_name(input)?;
- let (input, _) = ch('@')(input)?;
- let at_version_input = input;
- let (input, version) = parse_version(input)?;
- match Version::parse_from_npm(version) {
- Ok(version) => Ok((input, (name.to_string(), version))),
- Err(err) => ParseError::fail(at_version_input, format!("{err:#}")),
- }
- }
-
- fn parse_level_at_level<'a>(
- level: usize,
- ) -> impl Fn(&'a str) -> ParseResult<'a, ()> {
- fn parse_level(input: &str) -> ParseResult<usize> {
- let level = input.chars().take_while(|c| *c == '_').count();
- Ok((&input[level..], level))
- }
-
- move |input| {
- let (input, parsed_level) = parse_level(input)?;
- if parsed_level == level {
- Ok((input, ()))
- } else {
- ParseError::backtrace()
- }
- }
- }
-
- fn parse_peers_at_level<'a>(
- level: usize,
- ) -> impl Fn(&'a str) -> ParseResult<'a, Vec<NpmPackageId>> {
- move |mut input| {
- let mut peers = Vec::new();
- while let Ok((level_input, _)) = parse_level_at_level(level)(input) {
- input = level_input;
- let peer_result = parse_id_at_level(level)(input)?;
- input = peer_result.0;
- peers.push(peer_result.1);
- }
- Ok((input, peers))
- }
- }
-
- fn parse_id_at_level<'a>(
- level: usize,
- ) -> impl Fn(&'a str) -> ParseResult<'a, NpmPackageId> {
- move |input| {
- let (input, (name, version)) = parse_name_and_version(input)?;
- let name = if level > 0 {
- name.replace('+', "/")
- } else {
- name
- };
- let (input, peer_dependencies) =
- parse_peers_at_level(level + 1)(input)?;
- Ok((
- input,
- NpmPackageId {
- nv: NpmPackageNv { name, version },
- peer_dependencies,
- },
- ))
- }
- }
-
- with_failure_handling(parse_id_at_level(0))(id).map_err(|err| {
- NpmPackageNodeIdDeserializationError {
- message: format!("{err:#}"),
- text: id.to_string(),
- }
- })
- }
-}
-
-impl Ord for NpmPackageId {
- fn cmp(&self, other: &Self) -> Ordering {
- match self.nv.cmp(&other.nv) {
- Ordering::Equal => self.peer_dependencies.cmp(&other.peer_dependencies),
- ordering => ordering,
- }
- }
-}
-
-impl PartialOrd for NpmPackageId {
- fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
- Some(self.cmp(other))
- }
-}
-
-#[derive(Clone, Serialize, Deserialize, PartialEq, Eq)]
-pub struct NpmResolutionPackage {
- pub pkg_id: NpmPackageId,
- /// The peer dependency resolution can differ for the same
- /// package (name and version) depending on where it is in
- /// the resolution tree. This copy index indicates which
- /// copy of the package this is.
- pub copy_index: usize,
- pub dist: NpmPackageVersionDistInfo,
- /// Key is what the package refers to the other package as,
- /// which could be different from the package name.
- pub dependencies: HashMap<String, NpmPackageId>,
-}
-
-impl std::fmt::Debug for NpmResolutionPackage {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- // custom debug implementation for deterministic output in the tests
- f.debug_struct("NpmResolutionPackage")
- .field("pkg_id", &self.pkg_id)
- .field("copy_index", &self.copy_index)
- .field("dist", &self.dist)
- .field(
- "dependencies",
- &self.dependencies.iter().collect::<BTreeMap<_, _>>(),
- )
- .finish()
- }
-}
-
-impl NpmResolutionPackage {
- pub fn get_package_cache_folder_id(&self) -> NpmPackageCacheFolderId {
- NpmPackageCacheFolderId {
- nv: self.pkg_id.nv.clone(),
- copy_index: self.copy_index,
- }
- }
-}
-
-/// Handles updating and storing npm resolution in memory.
-///
-/// This does not interact with the file system.
-#[derive(Clone)]
-pub struct NpmResolution(Arc<NpmResolutionInner>);
-
-struct NpmResolutionInner {
- api: NpmRegistryApi,
- snapshot: RwLock<NpmResolutionSnapshot>,
- update_queue: TaskQueue,
- maybe_lockfile: Option<Arc<Mutex<Lockfile>>>,
-}
-
-impl std::fmt::Debug for NpmResolution {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- let snapshot = self.0.snapshot.read();
- f.debug_struct("NpmResolution")
- .field("snapshot", &snapshot)
- .finish()
- }
-}
-
-impl NpmResolution {
- pub fn new(
- api: NpmRegistryApi,
- initial_snapshot: Option<NpmResolutionSnapshot>,
- maybe_lockfile: Option<Arc<Mutex<Lockfile>>>,
- ) -> Self {
- Self(Arc::new(NpmResolutionInner {
- api,
- snapshot: RwLock::new(initial_snapshot.unwrap_or_default()),
- update_queue: Default::default(),
- maybe_lockfile,
- }))
- }
-
- pub async fn add_package_reqs(
- &self,
- package_reqs: Vec<NpmPackageReq>,
- ) -> Result<(), AnyError> {
- let inner = &self.0;
-
- // only allow one thread in here at a time
- let _permit = inner.update_queue.acquire().await;
- let snapshot = inner.snapshot.read().clone();
-
- let snapshot = add_package_reqs_to_snapshot(
- &inner.api,
- package_reqs,
- snapshot,
- self.0.maybe_lockfile.clone(),
- )
- .await?;
-
- *inner.snapshot.write() = snapshot;
- Ok(())
- }
-
- pub async fn set_package_reqs(
- &self,
- package_reqs: Vec<NpmPackageReq>,
- ) -> Result<(), AnyError> {
- let inner = &self.0;
- // only allow one thread in here at a time
- let _permit = inner.update_queue.acquire().await;
- let snapshot = inner.snapshot.read().clone();
-
- let reqs_set = package_reqs.iter().collect::<HashSet<_>>();
- let has_removed_package = !snapshot
- .package_reqs
- .keys()
- .all(|req| reqs_set.contains(req));
- // if any packages were removed, we need to completely recreate the npm resolution snapshot
- let snapshot = if has_removed_package {
- NpmResolutionSnapshot::default()
- } else {
- snapshot
- };
- let snapshot = add_package_reqs_to_snapshot(
- &inner.api,
- package_reqs,
- snapshot,
- self.0.maybe_lockfile.clone(),
- )
- .await?;
-
- *inner.snapshot.write() = snapshot;
-
- Ok(())
- }
-
- pub async fn resolve_pending(&self) -> Result<(), AnyError> {
- let inner = &self.0;
- // only allow one thread in here at a time
- let _permit = inner.update_queue.acquire().await;
- let snapshot = inner.snapshot.read().clone();
-
- let snapshot = add_package_reqs_to_snapshot(
- &inner.api,
- Vec::new(),
- snapshot,
- self.0.maybe_lockfile.clone(),
- )
- .await?;
-
- *inner.snapshot.write() = snapshot;
-
- Ok(())
- }
-
- pub fn pkg_req_ref_to_nv_ref(
- &self,
- req_ref: NpmPackageReqReference,
- ) -> Result<NpmPackageNvReference, AnyError> {
- let node_id = self.resolve_pkg_id_from_pkg_req(&req_ref.req)?;
- Ok(NpmPackageNvReference {
- nv: node_id.nv,
- sub_path: req_ref.sub_path,
- })
- }
-
- pub fn resolve_package_cache_folder_id_from_id(
- &self,
- id: &NpmPackageId,
- ) -> Option<NpmPackageCacheFolderId> {
- self
- .0
- .snapshot
- .read()
- .package_from_id(id)
- .map(|p| p.get_package_cache_folder_id())
- }
-
- pub fn resolve_package_from_package(
- &self,
- name: &str,
- referrer: &NpmPackageCacheFolderId,
- ) -> Result<NpmResolutionPackage, AnyError> {
- self
- .0
- .snapshot
- .read()
- .resolve_package_from_package(name, referrer)
- .cloned()
- }
-
- /// Resolve a node package from a deno module.
- pub fn resolve_pkg_id_from_pkg_req(
- &self,
- req: &NpmPackageReq,
- ) -> Result<NpmPackageId, AnyError> {
- self
- .0
- .snapshot
- .read()
- .resolve_pkg_from_pkg_req(req)
- .map(|pkg| pkg.pkg_id.clone())
- }
-
- pub fn resolve_pkg_id_from_deno_module(
- &self,
- id: &NpmPackageNv,
- ) -> Result<NpmPackageId, AnyError> {
- self
- .0
- .snapshot
- .read()
- .resolve_package_from_deno_module(id)
- .map(|pkg| pkg.pkg_id.clone())
- }
-
- /// Resolves a package requirement for deno graph. This should only be
- /// called by deno_graph's NpmResolver or for resolving packages in
- /// a package.json
- pub fn resolve_package_req_as_pending(
- &self,
- pkg_req: &NpmPackageReq,
- ) -> Result<NpmPackageNv, AnyError> {
- let inner = &self.0;
- // we should always have this because it should have been cached before here
- let package_info =
- inner.api.get_cached_package_info(&pkg_req.name).unwrap();
-
- let mut snapshot = inner.snapshot.write();
- let version_req =
- pkg_req.version_req.as_ref().unwrap_or(&*LATEST_VERSION_REQ);
- let version_and_info =
- match snapshot.packages_by_name.get(&package_info.name) {
- Some(existing_versions) => resolve_best_package_version_and_info(
- version_req,
- &package_info,
- existing_versions.iter().map(|p| &p.nv.version),
- )?,
- None => resolve_best_package_version_and_info(
- version_req,
- &package_info,
- Vec::new().iter(),
- )?,
- };
- let id = NpmPackageNv {
- name: package_info.name.to_string(),
- version: version_and_info.version,
- };
- debug!(
- "Resolved {}@{} to {}",
- pkg_req.name,
- version_req.version_text(),
- id.to_string(),
- );
- snapshot.package_reqs.insert(pkg_req.clone(), id.clone());
- let packages_with_name = snapshot
- .packages_by_name
- .entry(package_info.name.clone())
- .or_default();
- if !packages_with_name.iter().any(|p| p.nv == id) {
- packages_with_name.push(NpmPackageId {
- nv: id.clone(),
- peer_dependencies: Vec::new(),
- });
- }
- snapshot.pending_unresolved_packages.push(id.clone());
- Ok(id)
- }
-
- pub fn all_packages_partitioned(&self) -> NpmPackagesPartitioned {
- self.0.snapshot.read().all_packages_partitioned()
- }
-
- pub fn has_packages(&self) -> bool {
- !self.0.snapshot.read().packages.is_empty()
- }
-
- pub fn snapshot(&self) -> NpmResolutionSnapshot {
- self.0.snapshot.read().clone()
- }
-
- pub fn lock(&self, lockfile: &mut Lockfile) -> Result<(), AnyError> {
- let snapshot = self.0.snapshot.read();
- for (package_req, nv) in snapshot.package_reqs.iter() {
- lockfile.insert_npm_specifier(
- package_req.to_string(),
- snapshot.root_packages.get(nv).unwrap().as_serialized(),
- );
- }
- for package in snapshot.all_packages() {
- lockfile.check_or_insert_npm_package(package.into())?;
- }
- Ok(())
- }
-}
-
-async fn add_package_reqs_to_snapshot(
- api: &NpmRegistryApi,
- package_reqs: Vec<NpmPackageReq>,
- snapshot: NpmResolutionSnapshot,
- maybe_lockfile: Option<Arc<Mutex<Lockfile>>>,
-) -> Result<NpmResolutionSnapshot, AnyError> {
- if snapshot.pending_unresolved_packages.is_empty()
- && package_reqs
- .iter()
- .all(|req| snapshot.package_reqs.contains_key(req))
- {
- return Ok(snapshot); // already up to date
- }
-
- // convert the snapshot to a traversable graph
- let mut graph = Graph::from_snapshot(snapshot).with_context(|| {
- deno_core::anyhow::anyhow!(
- "Failed creating npm state. Try recreating your lockfile."
- )
- })?;
- let pending_unresolved = graph.take_pending_unresolved();
-
- // avoid loading the info if this is already in the graph
- let package_reqs = package_reqs
- .into_iter()
- .filter(|r| !graph.has_package_req(r))
- .collect::<Vec<_>>();
- let pending_unresolved = pending_unresolved
- .into_iter()
- .filter(|p| !graph.has_root_package(p))
- .collect::<Vec<_>>();
-
- // cache the packages in parallel
- api
- .cache_in_parallel(
- package_reqs
- .iter()
- .map(|req| req.name.clone())
- .chain(pending_unresolved.iter().map(|id| id.name.clone()))
- .collect::<HashSet<_>>()
- .into_iter()
- .collect::<Vec<_>>(),
- )
- .await?;
-
- // go over the top level package names first (npm package reqs and pending unresolved),
- // then down the tree one level at a time through all the branches
- let mut resolver = GraphDependencyResolver::new(&mut graph, api);
-
- // The package reqs and ids should already be sorted
- // in the order they should be resolved in.
- for package_req in package_reqs {
- let info = api.package_info(&package_req.name).await?;
- resolver.add_package_req(&package_req, &info)?;
- }
-
- for pkg_id in pending_unresolved {
- let info = api.package_info(&pkg_id.name).await?;
- resolver.add_root_package(&pkg_id, &info)?;
- }
-
- resolver.resolve_pending().await?;
-
- let result = graph.into_snapshot(api).await;
- api.clear_memory_cache();
-
- if let Some(lockfile_mutex) = maybe_lockfile {
- let mut lockfile = lockfile_mutex.lock();
- match result {
- Ok(snapshot) => {
- for (package_req, nv) in snapshot.package_reqs.iter() {
- lockfile.insert_npm_specifier(
- package_req.to_string(),
- snapshot.root_packages.get(nv).unwrap().as_serialized(),
- );
- }
- for package in snapshot.all_packages() {
- lockfile.check_or_insert_npm_package(package.into())?;
- }
- Ok(snapshot)
- }
- Err(err) => Err(err),
- }
- } else {
- result
- }
-}
-
-#[cfg(test)]
-mod test {
- use deno_graph::npm::NpmPackageNv;
- use deno_graph::semver::Version;
-
- use super::NpmPackageId;
-
- #[test]
- fn serialize_npm_package_id() {
- let id = NpmPackageId {
- nv: NpmPackageNv {
- name: "pkg-a".to_string(),
- version: Version::parse_from_npm("1.2.3").unwrap(),
- },
- peer_dependencies: vec![
- NpmPackageId {
- nv: NpmPackageNv {
- name: "pkg-b".to_string(),
- version: Version::parse_from_npm("3.2.1").unwrap(),
- },
- peer_dependencies: vec![
- NpmPackageId {
- nv: NpmPackageNv {
- name: "pkg-c".to_string(),
- version: Version::parse_from_npm("1.3.2").unwrap(),
- },
- peer_dependencies: vec![],
- },
- NpmPackageId {
- nv: NpmPackageNv {
- name: "pkg-d".to_string(),
- version: Version::parse_from_npm("2.3.4").unwrap(),
- },
- peer_dependencies: vec![],
- },
- ],
- },
- NpmPackageId {
- nv: NpmPackageNv {
- name: "pkg-e".to_string(),
- version: Version::parse_from_npm("2.3.1").unwrap(),
- },
- peer_dependencies: vec![NpmPackageId {
- nv: NpmPackageNv {
- name: "pkg-f".to_string(),
- version: Version::parse_from_npm("2.3.1").unwrap(),
- },
- peer_dependencies: vec![],
- }],
- },
- ],
- };
-
- // this shouldn't change because it's used in the lockfile
- let serialized = id.as_serialized();
- assert_eq!(serialized, "pkg-a@1.2.3_pkg-b@3.2.1__pkg-c@1.3.2__pkg-d@2.3.4_pkg-e@2.3.1__pkg-f@2.3.1");
- assert_eq!(NpmPackageId::from_serialized(&serialized).unwrap(), id);
- }
-}
diff --git a/cli/npm/resolution/snapshot.rs b/cli/npm/resolution/snapshot.rs
deleted file mode 100644
index e8df8286e..000000000
--- a/cli/npm/resolution/snapshot.rs
+++ /dev/null
@@ -1,492 +0,0 @@
-// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
-
-use std::collections::BTreeMap;
-use std::collections::HashMap;
-use std::collections::HashSet;
-use std::sync::Arc;
-
-use deno_core::anyhow::anyhow;
-use deno_core::anyhow::bail;
-use deno_core::anyhow::Context;
-use deno_core::error::AnyError;
-use deno_core::parking_lot::Mutex;
-use deno_graph::npm::NpmPackageNv;
-use deno_graph::npm::NpmPackageReq;
-use deno_graph::semver::VersionReq;
-use serde::Deserialize;
-use serde::Serialize;
-
-use crate::args::Lockfile;
-use crate::npm::cache::NpmPackageCacheFolderId;
-use crate::npm::registry::NpmPackageVersionDistInfo;
-use crate::npm::registry::NpmRegistryApi;
-
-use super::NpmPackageId;
-use super::NpmResolutionPackage;
-
-/// Packages partitioned by if they are "copy" packages or not.
-pub struct NpmPackagesPartitioned {
- pub packages: Vec<NpmResolutionPackage>,
- /// Since peer dependency resolution occurs based on ancestors and ancestor
- /// siblings, this may sometimes cause the same package (name and version)
- /// to have different dependencies based on where it appears in the tree.
- /// For these packages, we create a "copy package" or duplicate of the package
- /// whose dependencies are that of where in the tree they've resolved to.
- pub copy_packages: Vec<NpmResolutionPackage>,
-}
-
-impl NpmPackagesPartitioned {
- pub fn into_all(self) -> Vec<NpmResolutionPackage> {
- let mut packages = self.packages;
- packages.extend(self.copy_packages);
- packages
- }
-}
-
-#[derive(Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
-pub struct NpmResolutionSnapshot {
- /// The unique package requirements map to a single npm package name and version.
- #[serde(with = "map_to_vec")]
- pub(super) package_reqs: HashMap<NpmPackageReq, NpmPackageNv>,
- // Each root level npm package name and version maps to an exact npm package node id.
- #[serde(with = "map_to_vec")]
- pub(super) root_packages: HashMap<NpmPackageNv, NpmPackageId>,
- pub(super) packages_by_name: HashMap<String, Vec<NpmPackageId>>,
- #[serde(with = "map_to_vec")]
- pub(super) packages: HashMap<NpmPackageId, NpmResolutionPackage>,
- /// Ordered list based on resolution of packages whose dependencies
- /// have not yet been resolved
- pub(super) pending_unresolved_packages: Vec<NpmPackageNv>,
-}
-
-impl std::fmt::Debug for NpmResolutionSnapshot {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- // do a custom debug implementation that creates deterministic output for the tests
- f.debug_struct("NpmResolutionSnapshot")
- .field(
- "package_reqs",
- &self.package_reqs.iter().collect::<BTreeMap<_, _>>(),
- )
- .field(
- "root_packages",
- &self.root_packages.iter().collect::<BTreeMap<_, _>>(),
- )
- .field(
- "packages_by_name",
- &self.packages_by_name.iter().collect::<BTreeMap<_, _>>(),
- )
- .field(
- "packages",
- &self.packages.iter().collect::<BTreeMap<_, _>>(),
- )
- .field(
- "pending_unresolved_packages",
- &self.pending_unresolved_packages,
- )
- .finish()
- }
-}
-
-// This is done so the maps with non-string keys get serialized and deserialized as vectors.
-// Adapted from: https://github.com/serde-rs/serde/issues/936#issuecomment-302281792
-mod map_to_vec {
- use std::collections::HashMap;
-
- use serde::de::Deserialize;
- use serde::de::Deserializer;
- use serde::ser::Serializer;
- use serde::Serialize;
-
- pub fn serialize<S, K: Serialize, V: Serialize>(
- map: &HashMap<K, V>,
- serializer: S,
- ) -> Result<S::Ok, S::Error>
- where
- S: Serializer,
- {
- serializer.collect_seq(map.iter())
- }
-
- pub fn deserialize<
- 'de,
- D,
- K: Deserialize<'de> + Eq + std::hash::Hash,
- V: Deserialize<'de>,
- >(
- deserializer: D,
- ) -> Result<HashMap<K, V>, D::Error>
- where
- D: Deserializer<'de>,
- {
- let mut map = HashMap::new();
- for (key, value) in Vec::<(K, V)>::deserialize(deserializer)? {
- map.insert(key, value);
- }
- Ok(map)
- }
-}
-
-impl NpmResolutionSnapshot {
- /// Gets if this snapshot is empty.
- pub fn is_empty(&self) -> bool {
- self.packages.is_empty() && self.pending_unresolved_packages.is_empty()
- }
-
- /// Resolve a package from a package requirement.
- pub fn resolve_pkg_from_pkg_req(
- &self,
- req: &NpmPackageReq,
- ) -> Result<&NpmResolutionPackage, AnyError> {
- match self.package_reqs.get(req) {
- Some(id) => self.resolve_package_from_deno_module(id),
- None => bail!("could not find npm package directory for '{}'", req),
- }
- }
-
- /// Resolve a package from a deno module.
- pub fn resolve_package_from_deno_module(
- &self,
- id: &NpmPackageNv,
- ) -> Result<&NpmResolutionPackage, AnyError> {
- match self.root_packages.get(id) {
- Some(id) => Ok(self.packages.get(id).unwrap()),
- None => bail!("could not find npm package directory for '{}'", id),
- }
- }
-
- pub fn top_level_packages(&self) -> Vec<NpmPackageId> {
- self.root_packages.values().cloned().collect::<Vec<_>>()
- }
-
- pub fn package_from_id(
- &self,
- id: &NpmPackageId,
- ) -> Option<&NpmResolutionPackage> {
- self.packages.get(id)
- }
-
- pub fn resolve_package_from_package(
- &self,
- name: &str,
- referrer: &NpmPackageCacheFolderId,
- ) -> Result<&NpmResolutionPackage, AnyError> {
- // todo(dsherret): do we need an additional hashmap to get this quickly?
- let referrer_package = self
- .packages_by_name
- .get(&referrer.nv.name)
- .and_then(|packages| {
- packages
- .iter()
- .filter(|p| p.nv.version == referrer.nv.version)
- .filter_map(|node_id| {
- let package = self.packages.get(node_id)?;
- if package.copy_index == referrer.copy_index {
- Some(package)
- } else {
- None
- }
- })
- .next()
- })
- .ok_or_else(|| {
- anyhow!("could not find referrer npm package '{}'", referrer)
- })?;
-
- let name = name_without_path(name);
- if let Some(id) = referrer_package.dependencies.get(name) {
- return Ok(self.packages.get(id).unwrap());
- }
-
- if referrer_package.pkg_id.nv.name == name {
- return Ok(referrer_package);
- }
-
- // TODO(bartlomieju): this should use a reverse lookup table in the
- // snapshot instead of resolving best version again.
- let any_version_req = VersionReq::parse_from_npm("*").unwrap();
- if let Some(id) = self.resolve_best_package_id(name, &any_version_req) {
- if let Some(pkg) = self.packages.get(&id) {
- return Ok(pkg);
- }
- }
-
- bail!(
- "could not find npm package '{}' referenced by '{}'",
- name,
- referrer
- )
- }
-
- pub fn all_packages(&self) -> Vec<NpmResolutionPackage> {
- self.packages.values().cloned().collect()
- }
-
- pub fn all_packages_partitioned(&self) -> NpmPackagesPartitioned {
- let mut packages = self.all_packages();
- let mut copy_packages = Vec::with_capacity(packages.len() / 2); // at most 1 copy for every package
-
- // partition out any packages that are "copy" packages
- for i in (0..packages.len()).rev() {
- if packages[i].copy_index > 0 {
- copy_packages.push(packages.swap_remove(i));
- }
- }
-
- NpmPackagesPartitioned {
- packages,
- copy_packages,
- }
- }
-
- pub fn resolve_best_package_id(
- &self,
- name: &str,
- version_req: &VersionReq,
- ) -> Option<NpmPackageId> {
- // todo(dsherret): this is not exactly correct because some ids
- // will be better than others due to peer dependencies
- let mut maybe_best_id: Option<&NpmPackageId> = None;
- if let Some(node_ids) = self.packages_by_name.get(name) {
- for node_id in node_ids.iter() {
- if version_req.matches(&node_id.nv.version) {
- let is_best_version = maybe_best_id
- .as_ref()
- .map(|best_id| best_id.nv.version.cmp(&node_id.nv.version).is_lt())
- .unwrap_or(true);
- if is_best_version {
- maybe_best_id = Some(node_id);
- }
- }
- }
- }
- maybe_best_id.cloned()
- }
-
- pub async fn from_lockfile(
- lockfile: Arc<Mutex<Lockfile>>,
- api: &NpmRegistryApi,
- ) -> Result<Self, AnyError> {
- let mut package_reqs: HashMap<NpmPackageReq, NpmPackageNv>;
- let mut root_packages: HashMap<NpmPackageNv, NpmPackageId>;
- let mut packages_by_name: HashMap<String, Vec<NpmPackageId>>;
- let mut packages: HashMap<NpmPackageId, NpmResolutionPackage>;
- let mut copy_index_resolver: SnapshotPackageCopyIndexResolver;
-
- {
- let lockfile = lockfile.lock();
-
- // pre-allocate collections
- package_reqs =
- HashMap::with_capacity(lockfile.content.npm.specifiers.len());
- root_packages =
- HashMap::with_capacity(lockfile.content.npm.specifiers.len());
- let packages_len = lockfile.content.npm.packages.len();
- packages = HashMap::with_capacity(packages_len);
- packages_by_name = HashMap::with_capacity(packages_len); // close enough
- copy_index_resolver =
- SnapshotPackageCopyIndexResolver::with_capacity(packages_len);
- let mut verify_ids = HashSet::with_capacity(packages_len);
-
- // collect the specifiers to version mappings
- for (key, value) in &lockfile.content.npm.specifiers {
- let package_req = NpmPackageReq::from_str(key)
- .with_context(|| format!("Unable to parse npm specifier: {key}"))?;
- let package_id = NpmPackageId::from_serialized(value)?;
- package_reqs.insert(package_req, package_id.nv.clone());
- root_packages.insert(package_id.nv.clone(), package_id.clone());
- verify_ids.insert(package_id.clone());
- }
-
- // then the packages
- for (key, value) in &lockfile.content.npm.packages {
- let package_id = NpmPackageId::from_serialized(key)?;
-
- // collect the dependencies
- let mut dependencies = HashMap::default();
-
- packages_by_name
- .entry(package_id.nv.name.to_string())
- .or_default()
- .push(package_id.clone());
-
- for (name, specifier) in &value.dependencies {
- let dep_id = NpmPackageId::from_serialized(specifier)?;
- dependencies.insert(name.to_string(), dep_id.clone());
- verify_ids.insert(dep_id);
- }
-
- let package = NpmResolutionPackage {
- pkg_id: package_id.clone(),
- copy_index: copy_index_resolver.resolve(&package_id),
- // temporary dummy value
- dist: NpmPackageVersionDistInfo::default(),
- dependencies,
- };
-
- packages.insert(package_id, package);
- }
-
- // verify that all these ids exist in packages
- for id in &verify_ids {
- if !packages.contains_key(id) {
- bail!(
- "the lockfile is corrupt. You can recreate it with --lock-write"
- );
- }
- }
- }
-
- api
- .cache_in_parallel(packages_by_name.keys().cloned().collect())
- .await?;
-
- // ensure the dist is set for each package
- for package in packages.values_mut() {
- // this will read from the memory cache now
- let version_info = match api
- .package_version_info(&package.pkg_id.nv)
- .await?
- {
- Some(version_info) => version_info,
- None => {
- bail!("could not find '{}' specified in the lockfile. Maybe try again with --reload", package.pkg_id.nv);
- }
- };
- package.dist = version_info.dist;
- }
-
- Ok(Self {
- package_reqs,
- root_packages,
- packages_by_name,
- packages,
- pending_unresolved_packages: Default::default(),
- })
- }
-}
-
-pub struct SnapshotPackageCopyIndexResolver {
- packages_to_copy_index: HashMap<NpmPackageId, usize>,
- package_name_version_to_copy_count: HashMap<NpmPackageNv, usize>,
-}
-
-impl SnapshotPackageCopyIndexResolver {
- pub fn with_capacity(capacity: usize) -> Self {
- Self {
- packages_to_copy_index: HashMap::with_capacity(capacity),
- package_name_version_to_copy_count: HashMap::with_capacity(capacity), // close enough
- }
- }
-
- pub fn from_map_with_capacity(
- mut packages_to_copy_index: HashMap<NpmPackageId, usize>,
- capacity: usize,
- ) -> Self {
- let mut package_name_version_to_copy_count =
- HashMap::with_capacity(capacity); // close enough
- if capacity > packages_to_copy_index.len() {
- packages_to_copy_index.reserve(capacity - packages_to_copy_index.len());
- }
-
- for (node_id, index) in &packages_to_copy_index {
- let entry = package_name_version_to_copy_count
- .entry(node_id.nv.clone())
- .or_insert(0);
- if *entry < *index {
- *entry = *index;
- }
- }
- Self {
- packages_to_copy_index,
- package_name_version_to_copy_count,
- }
- }
-
- pub fn resolve(&mut self, node_id: &NpmPackageId) -> usize {
- if let Some(index) = self.packages_to_copy_index.get(node_id) {
- *index
- } else {
- let index = *self
- .package_name_version_to_copy_count
- .entry(node_id.nv.clone())
- .and_modify(|count| {
- *count += 1;
- })
- .or_insert(0);
- self.packages_to_copy_index.insert(node_id.clone(), index);
- index
- }
- }
-}
-
-fn name_without_path(name: &str) -> &str {
- let mut search_start_index = 0;
- if name.starts_with('@') {
- if let Some(slash_index) = name.find('/') {
- search_start_index = slash_index + 1;
- }
- }
- if let Some(slash_index) = &name[search_start_index..].find('/') {
- // get the name up until the path slash
- &name[0..search_start_index + slash_index]
- } else {
- name
- }
-}
-
-#[cfg(test)]
-mod tests {
- use super::*;
-
- #[test]
- fn test_name_without_path() {
- assert_eq!(name_without_path("foo"), "foo");
- assert_eq!(name_without_path("@foo/bar"), "@foo/bar");
- assert_eq!(name_without_path("@foo/bar/baz"), "@foo/bar");
- assert_eq!(name_without_path("@hello"), "@hello");
- }
-
- #[test]
- fn test_copy_index_resolver() {
- let mut copy_index_resolver =
- SnapshotPackageCopyIndexResolver::with_capacity(10);
- assert_eq!(
- copy_index_resolver
- .resolve(&NpmPackageId::from_serialized("package@1.0.0").unwrap()),
- 0
- );
- assert_eq!(
- copy_index_resolver
- .resolve(&NpmPackageId::from_serialized("package@1.0.0").unwrap()),
- 0
- );
- assert_eq!(
- copy_index_resolver.resolve(
- &NpmPackageId::from_serialized("package@1.0.0_package-b@1.0.0")
- .unwrap()
- ),
- 1
- );
- assert_eq!(
- copy_index_resolver.resolve(
- &NpmPackageId::from_serialized(
- "package@1.0.0_package-b@1.0.0__package-c@2.0.0"
- )
- .unwrap()
- ),
- 2
- );
- assert_eq!(
- copy_index_resolver.resolve(
- &NpmPackageId::from_serialized("package@1.0.0_package-b@1.0.0")
- .unwrap()
- ),
- 1
- );
- assert_eq!(
- copy_index_resolver
- .resolve(&NpmPackageId::from_serialized("package-b@1.0.0").unwrap()),
- 0
- );
- }
-}
diff --git a/cli/npm/resolvers/common.rs b/cli/npm/resolvers/common.rs
index a8e822bb9..8b8be5ce2 100644
--- a/cli/npm/resolvers/common.rs
+++ b/cli/npm/resolvers/common.rs
@@ -9,13 +9,13 @@ use deno_ast::ModuleSpecifier;
use deno_core::error::AnyError;
use deno_core::futures;
use deno_core::url::Url;
+use deno_npm::NpmPackageId;
+use deno_npm::NpmResolutionPackage;
use deno_runtime::deno_node::NodePermissions;
use deno_runtime::deno_node::NodeResolutionMode;
use crate::npm::cache::should_sync_download;
use crate::npm::NpmCache;
-use crate::npm::NpmPackageId;
-use crate::npm::NpmResolutionPackage;
/// Part of the resolution that interacts with the file system.
#[async_trait]
diff --git a/cli/npm/resolvers/global.rs b/cli/npm/resolvers/global.rs
index 5d5334299..518a9110a 100644
--- a/cli/npm/resolvers/global.rs
+++ b/cli/npm/resolvers/global.rs
@@ -9,15 +9,15 @@ use async_trait::async_trait;
use deno_ast::ModuleSpecifier;
use deno_core::error::AnyError;
use deno_core::url::Url;
+use deno_npm::NpmPackageCacheFolderId;
+use deno_npm::NpmPackageId;
+use deno_npm::NpmResolutionPackage;
use deno_runtime::deno_node::NodePermissions;
use deno_runtime::deno_node::NodeResolutionMode;
-use crate::npm::cache::NpmPackageCacheFolderId;
use crate::npm::resolution::NpmResolution;
use crate::npm::resolvers::common::cache_packages;
use crate::npm::NpmCache;
-use crate::npm::NpmPackageId;
-use crate::npm::NpmResolutionPackage;
use super::common::ensure_registry_read_permission;
use super::common::types_package_name;
diff --git a/cli/npm/resolvers/local.rs b/cli/npm/resolvers/local.rs
index f9cc9b09b..59d8b0829 100644
--- a/cli/npm/resolvers/local.rs
+++ b/cli/npm/resolvers/local.rs
@@ -19,6 +19,9 @@ use deno_core::anyhow::bail;
use deno_core::anyhow::Context;
use deno_core::error::AnyError;
use deno_core::url::Url;
+use deno_npm::resolution::NpmResolutionSnapshot;
+use deno_npm::NpmPackageCacheFolderId;
+use deno_npm::NpmPackageId;
use deno_runtime::deno_core::futures;
use deno_runtime::deno_node::NodePermissions;
use deno_runtime::deno_node::NodeResolutionMode;
@@ -27,11 +30,8 @@ use tokio::task::JoinHandle;
use crate::npm::cache::mixed_case_package_name_encode;
use crate::npm::cache::should_sync_download;
-use crate::npm::cache::NpmPackageCacheFolderId;
use crate::npm::resolution::NpmResolution;
-use crate::npm::resolution::NpmResolutionSnapshot;
use crate::npm::NpmCache;
-use crate::npm::NpmPackageId;
use crate::util::fs::copy_dir_recursive;
use crate::util::fs::hard_link_dir_recursive;
@@ -386,12 +386,7 @@ async fn sync_resolution_with_fs(
// node_modules/.deno/<package_id>/node_modules/<package_name>
let mut found_names = HashSet::new();
let mut pending_packages = VecDeque::new();
- pending_packages.extend(
- snapshot
- .top_level_packages()
- .into_iter()
- .map(|id| (id, true)),
- );
+ pending_packages.extend(snapshot.top_level_packages().map(|id| (id, true)));
while let Some((id, is_top_level)) = pending_packages.pop_front() {
let root_folder_name = if found_names.insert(id.nv.name.clone()) {
id.nv.name.clone()
@@ -400,7 +395,7 @@ async fn sync_resolution_with_fs(
} else {
continue; // skip, already handled
};
- let package = snapshot.package_from_id(&id).unwrap();
+ let package = snapshot.package_from_id(id).unwrap();
let local_registry_package_path = join_package_name(
&deno_local_registry_dir
.join(get_package_folder_id_folder_name(
@@ -415,7 +410,7 @@ async fn sync_resolution_with_fs(
&join_package_name(root_node_modules_dir_path, &root_folder_name),
)?;
for id in package.dependencies.values() {
- pending_packages.push_back((id.clone(), false));
+ pending_packages.push_back((id, false));
}
}
diff --git a/cli/npm/resolvers/mod.rs b/cli/npm/resolvers/mod.rs
index c8ac6f44a..c958743dc 100644
--- a/cli/npm/resolvers/mod.rs
+++ b/cli/npm/resolvers/mod.rs
@@ -4,26 +4,29 @@ mod common;
mod global;
mod local;
+use std::path::Path;
+use std::path::PathBuf;
+use std::sync::Arc;
+
use deno_ast::ModuleSpecifier;
use deno_core::anyhow::bail;
use deno_core::error::AnyError;
use deno_core::parking_lot::Mutex;
use deno_core::serde_json;
use deno_core::url::Url;
-use deno_graph::npm::NpmPackageNv;
-use deno_graph::npm::NpmPackageNvReference;
-use deno_graph::npm::NpmPackageReq;
-use deno_graph::npm::NpmPackageReqReference;
+use deno_npm::resolution::NpmResolutionSnapshot;
+use deno_npm::NpmPackageId;
use deno_runtime::deno_node::NodePermissions;
use deno_runtime::deno_node::NodeResolutionMode;
use deno_runtime::deno_node::PathClean;
use deno_runtime::deno_node::RequireNpmResolver;
+use deno_semver::npm::NpmPackageNv;
+use deno_semver::npm::NpmPackageNvReference;
+use deno_semver::npm::NpmPackageReq;
+use deno_semver::npm::NpmPackageReqReference;
use global::GlobalNpmPackageResolver;
use serde::Deserialize;
use serde::Serialize;
-use std::path::Path;
-use std::path::PathBuf;
-use std::sync::Arc;
use crate::args::Lockfile;
use crate::util::fs::canonicalize_path_maybe_not_exists;
@@ -33,8 +36,6 @@ use self::common::NpmPackageFsResolver;
use self::local::LocalNpmPackageResolver;
use super::resolution::NpmResolution;
use super::NpmCache;
-use super::NpmPackageId;
-use super::NpmResolutionSnapshot;
/// State provided to the process via an environment variable.
#[derive(Clone, Debug, Serialize, Deserialize)]
diff --git a/cli/npm/tarball.rs b/cli/npm/tarball.rs
index 1f804a9aa..ce1ac3339 100644
--- a/cli/npm/tarball.rs
+++ b/cli/npm/tarball.rs
@@ -7,13 +7,13 @@ use std::path::PathBuf;
use deno_core::anyhow::bail;
use deno_core::error::AnyError;
-use deno_graph::npm::NpmPackageNv;
+use deno_npm::registry::NpmPackageVersionDistInfo;
+use deno_semver::npm::NpmPackageNv;
use flate2::read::GzDecoder;
use tar::Archive;
use tar::EntryType;
use super::cache::with_folder_sync_lock;
-use super::registry::NpmPackageVersionDistInfo;
pub fn verify_and_extract_tarball(
package: &NpmPackageNv,
@@ -116,7 +116,7 @@ fn extract_tarball(data: &[u8], output_folder: &Path) -> Result<(), AnyError> {
#[cfg(test)]
mod test {
- use deno_graph::semver::Version;
+ use deno_semver::Version;
use super::*;