diff options
author | David Sherret <dsherret@users.noreply.github.com> | 2022-10-21 11:20:18 -0400 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-10-21 15:20:18 +0000 |
commit | bcfe279fba865763c87f9cd8d5a2d0b2cbf451be (patch) | |
tree | 68e4d1bc52e261df50279f9ecea14795d1c46f6c /cli/npm/resolvers | |
parent | 0e1a71fec6fff5fe62d7e6b2bfffb7ab877d7b71 (diff) |
feat(unstable/npm): initial type checking of npm specifiers (#16332)
Diffstat (limited to 'cli/npm/resolvers')
-rw-r--r-- | cli/npm/resolvers/common.rs | 7 | ||||
-rw-r--r-- | cli/npm/resolvers/global.rs | 60 | ||||
-rw-r--r-- | cli/npm/resolvers/local.rs | 58 | ||||
-rw-r--r-- | cli/npm/resolvers/mod.rs | 83 |
4 files changed, 179 insertions, 29 deletions
diff --git a/cli/npm/resolvers/common.rs b/cli/npm/resolvers/common.rs index b91deae9e..4981d5613 100644 --- a/cli/npm/resolvers/common.rs +++ b/cli/npm/resolvers/common.rs @@ -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; diff --git a/cli/npm/resolvers/global.rs b/cli/npm/resolvers/global.rs index c1b6818fd..8eafc19f4 100644 --- a/cli/npm/resolvers/global.rs +++ b/cli/npm/resolvers/global.rs @@ -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 +} diff --git a/cli/npm/resolvers/local.rs b/cli/npm/resolvers/local.rs index cd79320b7..b51593d4c 100644 --- a/cli/npm/resolvers/local.rs +++ b/cli/npm/resolvers/local.rs @@ -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, diff --git a/cli/npm/resolvers/mod.rs b/cli/npm/resolvers/mod.rs index d290a5569..5498bbf75 100644 --- a/cli/npm/resolvers/mod.rs +++ b/cli/npm/resolvers/mod.rs @@ -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 { @@ -77,30 +91,53 @@ impl NpmPackageResolver { 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(|| { process_npm_state .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( |