1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-11-02 09:34:19 -04:00
denoland-deno/cli/npm/resolvers/common.rs
David Sherret cbb3f85433
feat(unstable/npm): support peer dependencies (#16561)
This adds support for peer dependencies in npm packages.

1. If not found higher in the tree (ancestor and ancestor siblings),
peer dependencies are resolved like a dependency similar to npm 7.
2. Optional peer dependencies are only resolved if found higher in the
tree.
3. This creates "copy packages" or duplicates of a package when a
package has different resolution due to peer dependency resolution—see
https://pnpm.io/how-peers-are-resolved. Unlike pnpm though, duplicates
of packages will have `_1`, `_2`, etc. added to the end of the package
version in the directory in order to minimize the chance of hitting the
max file path limit on Windows. This is done for both the local
"node_modules" directory and also the global npm cache. The files are
hard linked in this case to reduce hard drive space.

This is a first pass and the code is definitely more inefficient than it
could be.

Closes #15823
2022-11-08 14:17:24 -05:00

130 lines
3.4 KiB
Rust

// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license.
use std::collections::HashSet;
use std::io::ErrorKind;
use std::path::Path;
use std::path::PathBuf;
use deno_ast::ModuleSpecifier;
use deno_core::error::AnyError;
use deno_core::futures;
use deno_core::futures::future::BoxFuture;
use deno_core::url::Url;
use crate::lockfile::Lockfile;
use crate::npm::cache::should_sync_download;
use crate::npm::resolution::NpmResolutionSnapshot;
use crate::npm::NpmCache;
use crate::npm::NpmPackageId;
use crate::npm::NpmPackageReq;
use crate::npm::NpmResolutionPackage;
pub trait InnerNpmPackageResolver: Send + Sync {
fn resolve_package_folder_from_deno_module(
&self,
pkg_req: &NpmPackageReq,
) -> Result<PathBuf, AnyError>;
fn resolve_package_folder_from_package(
&self,
name: &str,
referrer: &ModuleSpecifier,
conditions: &[&str],
) -> Result<PathBuf, AnyError>;
fn resolve_package_folder_from_specifier(
&self,
specifier: &ModuleSpecifier,
) -> Result<PathBuf, AnyError>;
fn package_size(&self, package_id: &NpmPackageId) -> Result<u64, AnyError>;
fn has_packages(&self) -> bool;
fn add_package_reqs(
&self,
packages: Vec<NpmPackageReq>,
) -> BoxFuture<'static, Result<(), AnyError>>;
fn set_package_reqs(
&self,
packages: HashSet<NpmPackageReq>,
) -> BoxFuture<'static, Result<(), AnyError>>;
fn ensure_read_permission(&self, path: &Path) -> Result<(), AnyError>;
fn snapshot(&self) -> NpmResolutionSnapshot;
fn lock(&self, lockfile: &mut Lockfile) -> Result<(), AnyError>;
}
/// Caches all the packages in parallel.
pub async fn cache_packages(
mut packages: Vec<NpmResolutionPackage>,
cache: &NpmCache,
registry_url: &Url,
) -> Result<(), AnyError> {
let sync_download = should_sync_download();
if sync_download {
// we're running the tests not with --quiet
// and we want the output to be deterministic
packages.sort_by(|a, b| a.id.cmp(&b.id));
}
let mut handles = Vec::with_capacity(packages.len());
for package in packages {
assert_eq!(package.copy_index, 0); // the caller should not provide any of these
let cache = cache.clone();
let registry_url = registry_url.clone();
let handle = tokio::task::spawn(async move {
cache
.ensure_package(
(package.id.name.as_str(), &package.id.version),
&package.dist,
&registry_url,
)
.await
});
if sync_download {
handle.await??;
} else {
handles.push(handle);
}
}
let results = futures::future::join_all(handles).await;
for result in results {
// surface the first error
result??;
}
Ok(())
}
pub fn ensure_registry_read_permission(
registry_path: &Path,
path: &Path,
) -> Result<(), AnyError> {
// allow reading if it's in the node_modules
if path.starts_with(&registry_path)
&& path
.components()
.all(|c| !matches!(c, std::path::Component::ParentDir))
{
// todo(dsherret): cache this?
if let Ok(registry_path) = std::fs::canonicalize(registry_path) {
match std::fs::canonicalize(path) {
Ok(path) if path.starts_with(registry_path) => {
return Ok(());
}
Err(e) if e.kind() == ErrorKind::NotFound => {
return Ok(());
}
_ => {} // ignore
}
}
}
Err(deno_core::error::custom_error(
"PermissionDenied",
format!("Reading {} is not allowed", path.display()),
))
}