feat(unstable/npm): initial type checking of npm specifiers (#16332)

This commit is contained in:
David Sherret 2022-10-21 11:20:18 -04:00 committed by GitHub
parent 0e1a71fec6
commit bcfe279fba
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
64 changed files with 2135 additions and 280 deletions

View file

@ -1,5 +1,6 @@
// 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;
@ -26,6 +27,7 @@ pub trait InnerNpmPackageResolver: Send + Sync {
&self,
name: &str,
referrer: &ModuleSpecifier,
conditions: &[&str],
) -> Result<PathBuf, AnyError>;
fn resolve_package_folder_from_specifier(
@ -40,6 +42,11 @@ pub trait InnerNpmPackageResolver: Send + Sync {
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;

View file

@ -2,6 +2,7 @@
//! Code for global npm cache resolution.
use std::collections::HashSet;
use std::path::Path;
use std::path::PathBuf;
use std::sync::Arc;
@ -11,6 +12,8 @@ use deno_core::error::AnyError;
use deno_core::futures::future::BoxFuture;
use deno_core::futures::FutureExt;
use deno_core::url::Url;
use deno_runtime::deno_node::PackageJson;
use deno_runtime::deno_node::TYPES_CONDITIONS;
use crate::npm::resolution::NpmResolution;
use crate::npm::resolution::NpmResolutionSnapshot;
@ -65,14 +68,35 @@ impl InnerNpmPackageResolver for GlobalNpmPackageResolver {
&self,
name: &str,
referrer: &ModuleSpecifier,
conditions: &[&str],
) -> Result<PathBuf, AnyError> {
let referrer_pkg_id = self
.cache
.resolve_package_id_from_specifier(referrer, &self.registry_url)?;
let pkg = self
let pkg_result = self
.resolution
.resolve_package_from_package(name, &referrer_pkg_id)?;
Ok(self.package_folder(&pkg.id))
.resolve_package_from_package(name, &referrer_pkg_id);
if conditions == TYPES_CONDITIONS && !name.starts_with("@types/") {
// When doing types resolution, the package must contain a "types"
// entry, or else it will then search for a @types package
if let Ok(pkg) = pkg_result {
let package_folder = self.package_folder(&pkg.id);
let package_json = PackageJson::load_skip_read_permission(
package_folder.join("package.json"),
)?;
if package_json.types.is_some() {
return Ok(package_folder);
}
}
let name = format!("@types/{}", name);
let pkg = self
.resolution
.resolve_package_from_package(&name, &referrer_pkg_id)?;
Ok(self.package_folder(&pkg.id))
} else {
Ok(self.package_folder(&pkg_result?.id))
}
}
fn resolve_package_folder_from_specifier(
@ -96,12 +120,19 @@ impl InnerNpmPackageResolver for GlobalNpmPackageResolver {
let resolver = self.clone();
async move {
resolver.resolution.add_package_reqs(packages).await?;
cache_packages(
resolver.resolution.all_packages(),
&resolver.cache,
&resolver.registry_url,
)
.await
cache_packages_in_resolver(&resolver).await
}
.boxed()
}
fn set_package_reqs(
&self,
packages: HashSet<NpmPackageReq>,
) -> BoxFuture<'static, Result<(), AnyError>> {
let resolver = self.clone();
async move {
resolver.resolution.set_package_reqs(packages).await?;
cache_packages_in_resolver(&resolver).await
}
.boxed()
}
@ -115,3 +146,14 @@ impl InnerNpmPackageResolver for GlobalNpmPackageResolver {
self.resolution.snapshot()
}
}
async fn cache_packages_in_resolver(
resolver: &GlobalNpmPackageResolver,
) -> Result<(), AnyError> {
cache_packages(
resolver.resolution.all_packages(),
&resolver.cache,
&resolver.registry_url,
)
.await
}

View file

@ -17,6 +17,8 @@ use deno_core::futures::future::BoxFuture;
use deno_core::futures::FutureExt;
use deno_core::url::Url;
use deno_runtime::deno_core::futures;
use deno_runtime::deno_node::PackageJson;
use deno_runtime::deno_node::TYPES_CONDITIONS;
use tokio::task::JoinHandle;
use crate::fs_util;
@ -124,6 +126,7 @@ impl InnerNpmPackageResolver for LocalNpmPackageResolver {
&self,
name: &str,
referrer: &ModuleSpecifier,
conditions: &[&str],
) -> Result<PathBuf, AnyError> {
let local_path = self.resolve_folder_for_specifier(referrer)?;
let package_root_path = self.resolve_package_root(&local_path);
@ -132,8 +135,28 @@ impl InnerNpmPackageResolver for LocalNpmPackageResolver {
current_folder = get_next_node_modules_ancestor(current_folder);
let sub_dir = join_package_name(current_folder, name);
if sub_dir.is_dir() {
return Ok(sub_dir);
// if doing types resolution, only resolve the package if it specifies a types property
if conditions == TYPES_CONDITIONS && !name.starts_with("@types/") {
let package_json = PackageJson::load_skip_read_permission(
sub_dir.join("package.json"),
)?;
if package_json.types.is_some() {
return Ok(sub_dir);
}
} else {
return Ok(sub_dir);
}
}
// if doing type resolution, check for the existance of a @types package
if conditions == TYPES_CONDITIONS && !name.starts_with("@types/") {
let sub_dir =
join_package_name(current_folder, &format!("@types/{}", name));
if sub_dir.is_dir() {
return Ok(sub_dir);
}
}
if current_folder == self.root_node_modules_path {
bail!(
"could not find package '{}' from referrer '{}'.",
@ -164,15 +187,20 @@ impl InnerNpmPackageResolver for LocalNpmPackageResolver {
let resolver = self.clone();
async move {
resolver.resolution.add_package_reqs(packages).await?;
sync_resolver_with_fs(&resolver).await?;
Ok(())
}
.boxed()
}
sync_resolution_with_fs(
&resolver.resolution.snapshot(),
&resolver.cache,
&resolver.registry_url,
&resolver.root_node_modules_path,
)
.await?;
fn set_package_reqs(
&self,
packages: HashSet<NpmPackageReq>,
) -> BoxFuture<'static, Result<(), AnyError>> {
let resolver = self.clone();
async move {
resolver.resolution.set_package_reqs(packages).await?;
sync_resolver_with_fs(&resolver).await?;
Ok(())
}
.boxed()
@ -187,6 +215,18 @@ impl InnerNpmPackageResolver for LocalNpmPackageResolver {
}
}
async fn sync_resolver_with_fs(
resolver: &LocalNpmPackageResolver,
) -> Result<(), AnyError> {
sync_resolution_with_fs(
&resolver.resolution.snapshot(),
&resolver.cache,
&resolver.registry_url,
&resolver.root_node_modules_path,
)
.await
}
/// Creates a pnpm style folder structure.
async fn sync_resolution_with_fs(
snapshot: &NpmResolutionSnapshot,

View file

@ -15,6 +15,7 @@ use global::GlobalNpmPackageResolver;
use once_cell::sync::Lazy;
use serde::Deserialize;
use serde::Serialize;
use std::collections::HashSet;
use std::path::Path;
use std::path::PathBuf;
use std::sync::Arc;
@ -23,10 +24,10 @@ use crate::fs_util;
use self::common::InnerNpmPackageResolver;
use self::local::LocalNpmPackageResolver;
use super::resolution::NpmResolutionSnapshot;
use super::NpmCache;
use super::NpmPackageReq;
use super::NpmRegistryApi;
use super::NpmResolutionSnapshot;
const RESOLUTION_STATE_ENV_VAR_NAME: &str =
"DENO_DONT_USE_INTERNAL_NODE_COMPAT_STATE";
@ -67,6 +68,19 @@ pub struct NpmPackageResolver {
no_npm: bool,
inner: Arc<dyn InnerNpmPackageResolver>,
local_node_modules_path: Option<PathBuf>,
api: NpmRegistryApi,
cache: NpmCache,
}
impl std::fmt::Debug for NpmPackageResolver {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("NpmPackageResolver")
.field("unstable", &self.unstable)
.field("no_npm", &self.no_npm)
.field("inner", &"<omitted>")
.field("local_node_modules_path", &self.local_node_modules_path)
.finish()
}
}
impl NpmPackageResolver {
@ -76,6 +90,24 @@ impl NpmPackageResolver {
unstable: bool,
no_npm: bool,
local_node_modules_path: Option<PathBuf>,
) -> Self {
Self::new_with_maybe_snapshot(
cache,
api,
unstable,
no_npm,
local_node_modules_path,
None,
)
}
fn new_with_maybe_snapshot(
cache: NpmCache,
api: NpmRegistryApi,
unstable: bool,
no_npm: bool,
local_node_modules_path: Option<PathBuf>,
initial_snapshot: Option<NpmResolutionSnapshot>,
) -> Self {
let process_npm_state = NpmProcessState::take();
let local_node_modules_path = local_node_modules_path.or_else(|| {
@ -83,24 +115,29 @@ impl NpmPackageResolver {
.as_ref()
.and_then(|s| s.local_node_modules_path.as_ref().map(PathBuf::from))
});
let maybe_snapshot = process_npm_state.map(|s| s.snapshot);
let maybe_snapshot =
initial_snapshot.or_else(|| process_npm_state.map(|s| s.snapshot));
let inner: Arc<dyn InnerNpmPackageResolver> = match &local_node_modules_path
{
Some(node_modules_folder) => Arc::new(LocalNpmPackageResolver::new(
cache,
api,
cache.clone(),
api.clone(),
node_modules_folder.clone(),
maybe_snapshot,
)),
None => {
Arc::new(GlobalNpmPackageResolver::new(cache, api, maybe_snapshot))
}
None => Arc::new(GlobalNpmPackageResolver::new(
cache.clone(),
api.clone(),
maybe_snapshot,
)),
};
Self {
unstable,
no_npm,
inner,
local_node_modules_path,
api,
cache,
}
}
@ -122,10 +159,11 @@ impl NpmPackageResolver {
&self,
name: &str,
referrer: &ModuleSpecifier,
conditions: &[&str],
) -> Result<PathBuf, AnyError> {
let path = self
.inner
.resolve_package_folder_from_package(name, referrer)?;
.resolve_package_folder_from_package(name, referrer, conditions)?;
log::debug!("Resolved {} from {} to {}", name, referrer, path.display());
Ok(path)
}
@ -156,12 +194,14 @@ impl NpmPackageResolver {
self.inner.has_packages()
}
/// Adds a package requirement to the resolver and ensures everything is setup.
/// Adds package requirements to the resolver and ensures everything is setup.
pub async fn add_package_reqs(
&self,
packages: Vec<NpmPackageReq>,
) -> Result<(), AnyError> {
assert!(!packages.is_empty());
if packages.is_empty() {
return Ok(());
}
if !self.unstable {
bail!(
@ -187,6 +227,14 @@ impl NpmPackageResolver {
self.inner.add_package_reqs(packages).await
}
/// Sets package requirements to the resolver, removing old requirements and adding new ones.
pub async fn set_package_reqs(
&self,
packages: HashSet<NpmPackageReq>,
) -> Result<(), AnyError> {
self.inner.set_package_reqs(packages).await
}
// If the main module should be treated as being in an npm package.
// This is triggered via a secret environment variable which is used
// for functionality like child_process.fork. Users should NOT depend
@ -206,6 +254,18 @@ impl NpmPackageResolver {
})
.unwrap()
}
/// Gets a new resolver with a new snapshotted state.
pub fn snapshotted(&self) -> Self {
Self::new_with_maybe_snapshot(
self.cache.clone(),
self.api.clone(),
self.unstable,
self.no_npm,
self.local_node_modules_path.clone(),
Some(self.inner.snapshot()),
)
}
}
impl RequireNpmResolver for NpmPackageResolver {
@ -213,9 +273,10 @@ impl RequireNpmResolver for NpmPackageResolver {
&self,
specifier: &str,
referrer: &std::path::Path,
conditions: &[&str],
) -> Result<PathBuf, AnyError> {
let referrer = path_to_specifier(referrer)?;
self.resolve_package_folder_from_package(specifier, &referrer)
self.resolve_package_folder_from_package(specifier, &referrer, conditions)
}
fn resolve_package_folder_from_path(