2023-04-06 18:46:44 -04:00
|
|
|
// 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;
|
2023-04-06 21:41:19 -04:00
|
|
|
use deno_npm::resolution::NpmPackageVersionResolutionError;
|
2023-04-06 18:46:44 -04:00
|
|
|
use deno_npm::resolution::NpmPackagesPartitioned;
|
2023-04-06 21:41:19 -04:00
|
|
|
use deno_npm::resolution::NpmResolutionError;
|
2023-04-06 18:46:44 -04:00
|
|
|
use deno_npm::resolution::NpmResolutionSnapshot;
|
2023-04-06 21:41:19 -04:00
|
|
|
use deno_npm::resolution::PackageNotFoundFromReferrerError;
|
|
|
|
use deno_npm::resolution::PackageNvNotFoundError;
|
|
|
|
use deno_npm::resolution::PackageReqNotFoundError;
|
2023-04-06 18:46:44 -04:00
|
|
|
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;
|
|
|
|
|
2023-04-12 08:36:11 -04:00
|
|
|
use super::registry::CliNpmRegistryApi;
|
2023-04-06 18:46:44 -04:00
|
|
|
|
|
|
|
/// 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 {
|
2023-04-12 08:36:11 -04:00
|
|
|
api: CliNpmRegistryApi,
|
2023-04-06 18:46:44 -04:00
|
|
|
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(
|
2023-04-12 08:36:11 -04:00
|
|
|
api: CliNpmRegistryApi,
|
2023-04-06 18:46:44 -04:00
|
|
|
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 = add_package_reqs_to_snapshot(
|
|
|
|
&inner.api,
|
|
|
|
package_reqs,
|
|
|
|
self.0.maybe_lockfile.clone(),
|
2023-04-06 21:41:19 -04:00
|
|
|
|| inner.snapshot.read().clone(),
|
2023-04-06 18:46:44 -04:00
|
|
|
)
|
|
|
|
.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;
|
2023-04-06 21:41:19 -04:00
|
|
|
|
|
|
|
let reqs_set = package_reqs.iter().cloned().collect::<HashSet<_>>();
|
2023-04-06 18:46:44 -04:00
|
|
|
let snapshot = add_package_reqs_to_snapshot(
|
|
|
|
&inner.api,
|
|
|
|
package_reqs,
|
|
|
|
self.0.maybe_lockfile.clone(),
|
2023-04-06 21:41:19 -04:00
|
|
|
|| {
|
|
|
|
let snapshot = inner.snapshot.read().clone();
|
|
|
|
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
|
|
|
|
if has_removed_package {
|
|
|
|
NpmResolutionSnapshot::default()
|
|
|
|
} else {
|
|
|
|
snapshot
|
|
|
|
}
|
|
|
|
},
|
2023-04-06 18:46:44 -04:00
|
|
|
)
|
|
|
|
.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 = add_package_reqs_to_snapshot(
|
|
|
|
&inner.api,
|
|
|
|
Vec::new(),
|
|
|
|
self.0.maybe_lockfile.clone(),
|
2023-04-06 21:41:19 -04:00
|
|
|
|| inner.snapshot.read().clone(),
|
2023-04-06 18:46:44 -04:00
|
|
|
)
|
|
|
|
.await?;
|
|
|
|
|
|
|
|
*inner.snapshot.write() = snapshot;
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn pkg_req_ref_to_nv_ref(
|
|
|
|
&self,
|
|
|
|
req_ref: NpmPackageReqReference,
|
2023-04-06 21:41:19 -04:00
|
|
|
) -> Result<NpmPackageNvReference, PackageReqNotFoundError> {
|
2023-04-06 18:46:44 -04:00
|
|
|
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,
|
2023-04-06 21:41:19 -04:00
|
|
|
) -> Result<NpmResolutionPackage, Box<PackageNotFoundFromReferrerError>> {
|
2023-04-06 18:46:44 -04:00
|
|
|
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,
|
2023-04-06 21:41:19 -04:00
|
|
|
) -> Result<NpmPackageId, PackageReqNotFoundError> {
|
2023-04-06 18:46:44 -04:00
|
|
|
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,
|
2023-04-06 21:41:19 -04:00
|
|
|
) -> Result<NpmPackageId, PackageNvNotFoundError> {
|
2023-04-06 18:46:44 -04:00
|
|
|
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,
|
2023-04-06 21:41:19 -04:00
|
|
|
) -> Result<NpmPackageNv, NpmPackageVersionResolutionError> {
|
2023-04-06 18:46:44 -04:00
|
|
|
// 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,
|
2023-04-06 21:41:19 -04:00
|
|
|
) -> Result<NpmPackageNv, NpmPackageVersionResolutionError> {
|
2023-04-06 18:46:44 -04:00
|
|
|
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(
|
2023-04-12 08:36:11 -04:00
|
|
|
api: &CliNpmRegistryApi,
|
2023-04-06 21:41:19 -04:00
|
|
|
// todo(18079): it should be possible to pass &[NpmPackageReq] in here
|
|
|
|
// and avoid all these clones, but the LSP complains because of its
|
|
|
|
// `Send` requirement
|
2023-04-06 18:46:44 -04:00
|
|
|
package_reqs: Vec<NpmPackageReq>,
|
|
|
|
maybe_lockfile: Option<Arc<Mutex<Lockfile>>>,
|
2023-04-06 21:41:19 -04:00
|
|
|
get_new_snapshot: impl Fn() -> NpmResolutionSnapshot,
|
2023-04-06 18:46:44 -04:00
|
|
|
) -> Result<NpmResolutionSnapshot, AnyError> {
|
2023-04-06 21:41:19 -04:00
|
|
|
let snapshot = get_new_snapshot();
|
2023-04-06 18:46:44 -04:00
|
|
|
if !snapshot.has_pending()
|
|
|
|
&& package_reqs
|
|
|
|
.iter()
|
|
|
|
.all(|req| snapshot.package_reqs().contains_key(req))
|
|
|
|
{
|
|
|
|
return Ok(snapshot); // already up to date
|
|
|
|
}
|
|
|
|
|
2023-04-06 21:41:19 -04:00
|
|
|
let result = snapshot.resolve_pending(package_reqs.clone(), api).await;
|
2023-04-06 18:46:44 -04:00
|
|
|
api.clear_memory_cache();
|
2023-04-06 21:41:19 -04:00
|
|
|
let snapshot = match result {
|
|
|
|
Ok(snapshot) => snapshot,
|
|
|
|
Err(NpmResolutionError::Resolution(err)) if api.mark_force_reload() => {
|
|
|
|
log::debug!("{err:#}");
|
|
|
|
log::debug!("npm resolution failed. Trying again...");
|
|
|
|
|
|
|
|
// try again
|
|
|
|
let snapshot = get_new_snapshot();
|
|
|
|
let result = snapshot.resolve_pending(package_reqs, api).await;
|
|
|
|
api.clear_memory_cache();
|
|
|
|
// now surface the result after clearing the cache
|
|
|
|
result?
|
|
|
|
}
|
|
|
|
Err(err) => return Err(err.into()),
|
|
|
|
};
|
2023-04-06 18:46:44 -04:00
|
|
|
|
|
|
|
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,
|
|
|
|
}
|
|
|
|
}
|