summaryrefslogtreecommitdiff
path: root/cli/npm/resolution/mod.rs
diff options
context:
space:
mode:
authorDavid Sherret <dsherret@users.noreply.github.com>2023-04-06 18:46:44 -0400
committerGitHub <noreply@github.com>2023-04-06 18:46:44 -0400
commitd07aa4a0723b04583b7cb1e09152457d866d13d3 (patch)
treef329a30becca95583fb71b4158c939c68228ce06 /cli/npm/resolution/mod.rs
parent1586c52b5b5ad511ec0bf896e94de8585f743cf8 (diff)
refactor(npm): use deno_npm and deno_semver (#18602)
Diffstat (limited to 'cli/npm/resolution/mod.rs')
-rw-r--r--cli/npm/resolution/mod.rs636
1 files changed, 0 insertions, 636 deletions
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);
- }
-}