mirror of
https://github.com/denoland/deno.git
synced 2025-08-04 02:48:24 +00:00
feat(unstable/npm): initial type checking of npm specifiers (#16332)
This commit is contained in:
parent
0e1a71fec6
commit
bcfe279fba
64 changed files with 2135 additions and 280 deletions
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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(
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue