diff options
Diffstat (limited to 'resolvers/deno/npm')
-rw-r--r-- | resolvers/deno/npm/byonm.rs | 62 | ||||
-rw-r--r-- | resolvers/deno/npm/mod.rs | 269 |
2 files changed, 316 insertions, 15 deletions
diff --git a/resolvers/deno/npm/byonm.rs b/resolvers/deno/npm/byonm.rs index 3394b3e50..e9182d47a 100644 --- a/resolvers/deno/npm/byonm.rs +++ b/resolvers/deno/npm/byonm.rs @@ -10,19 +10,22 @@ use deno_package_json::PackageJsonDepValue; use deno_path_util::url_to_file_path; use deno_semver::package::PackageReq; use deno_semver::Version; +use node_resolver::env::NodeResolverEnv; use node_resolver::errors::PackageFolderResolveError; use node_resolver::errors::PackageFolderResolveIoError; use node_resolver::errors::PackageJsonLoadError; use node_resolver::errors::PackageNotFoundError; -use node_resolver::load_pkg_json; -use node_resolver::NpmResolver; +use node_resolver::InNpmPackageChecker; +use node_resolver::NpmPackageFolderResolver; +use node_resolver::PackageJsonResolverRc; use thiserror::Error; use url::Url; -use crate::fs::DenoPkgJsonFsAdapter; use crate::fs::DenoResolverFs; use super::local::normalize_pkg_name_for_node_modules_deno_folder; +use super::CliNpmReqResolver; +use super::ResolvePkgFolderFromDenoReqError; #[derive(Debug, Error)] pub enum ByonmResolvePkgFolderFromDenoReqError { @@ -36,32 +39,41 @@ pub enum ByonmResolvePkgFolderFromDenoReqError { Io(#[from] std::io::Error), } -pub struct ByonmNpmResolverCreateOptions<Fs: DenoResolverFs> { - pub fs: Fs, +pub struct ByonmNpmResolverCreateOptions< + Fs: DenoResolverFs, + TEnv: NodeResolverEnv, +> { // todo(dsherret): investigate removing this pub root_node_modules_dir: Option<PathBuf>, + pub fs: Fs, + pub pkg_json_resolver: PackageJsonResolverRc<TEnv>, } #[derive(Debug)] -pub struct ByonmNpmResolver<Fs: DenoResolverFs> { +pub struct ByonmNpmResolver<Fs: DenoResolverFs, TEnv: NodeResolverEnv> { fs: Fs, + pkg_json_resolver: PackageJsonResolverRc<TEnv>, root_node_modules_dir: Option<PathBuf>, } -impl<Fs: DenoResolverFs + Clone> Clone for ByonmNpmResolver<Fs> { +impl<Fs: DenoResolverFs + Clone, TEnv: NodeResolverEnv> Clone + for ByonmNpmResolver<Fs, TEnv> +{ fn clone(&self) -> Self { Self { fs: self.fs.clone(), + pkg_json_resolver: self.pkg_json_resolver.clone(), root_node_modules_dir: self.root_node_modules_dir.clone(), } } } -impl<Fs: DenoResolverFs> ByonmNpmResolver<Fs> { - pub fn new(options: ByonmNpmResolverCreateOptions<Fs>) -> Self { +impl<Fs: DenoResolverFs, TEnv: NodeResolverEnv> ByonmNpmResolver<Fs, TEnv> { + pub fn new(options: ByonmNpmResolverCreateOptions<Fs, TEnv>) -> Self { Self { - fs: options.fs, root_node_modules_dir: options.root_node_modules_dir, + fs: options.fs, + pkg_json_resolver: options.pkg_json_resolver, } } @@ -73,7 +85,7 @@ impl<Fs: DenoResolverFs> ByonmNpmResolver<Fs> { &self, path: &Path, ) -> Result<Option<Arc<PackageJson>>, PackageJsonLoadError> { - load_pkg_json(&DenoPkgJsonFsAdapter(&self.fs), path) + self.pkg_json_resolver.load_package_json(path) } /// Finds the ancestor package.json that contains the specified dependency. @@ -290,8 +302,27 @@ impl<Fs: DenoResolverFs> ByonmNpmResolver<Fs> { } } -impl<Fs: DenoResolverFs + Send + Sync + std::fmt::Debug> NpmResolver - for ByonmNpmResolver<Fs> +impl< + Fs: DenoResolverFs + Send + Sync + std::fmt::Debug, + TEnv: NodeResolverEnv, + > CliNpmReqResolver for ByonmNpmResolver<Fs, TEnv> +{ + fn resolve_pkg_folder_from_deno_module_req( + &self, + req: &PackageReq, + referrer: &Url, + ) -> Result<PathBuf, ResolvePkgFolderFromDenoReqError> { + ByonmNpmResolver::resolve_pkg_folder_from_deno_module_req( + self, req, referrer, + ) + .map_err(ResolvePkgFolderFromDenoReqError::Byonm) + } +} + +impl< + Fs: DenoResolverFs + Send + Sync + std::fmt::Debug, + TEnv: NodeResolverEnv, + > NpmPackageFolderResolver for ByonmNpmResolver<Fs, TEnv> { fn resolve_package_folder_from_package( &self, @@ -342,7 +373,12 @@ impl<Fs: DenoResolverFs + Send + Sync + std::fmt::Debug> NpmResolver .into() }) } +} + +#[derive(Debug)] +pub struct ByonmInNpmPackageChecker; +impl InNpmPackageChecker for ByonmInNpmPackageChecker { fn in_npm_package(&self, specifier: &Url) -> bool { specifier.scheme() == "file" && specifier diff --git a/resolvers/deno/npm/mod.rs b/resolvers/deno/npm/mod.rs index 9d885cad3..09e35b15c 100644 --- a/resolvers/deno/npm/mod.rs +++ b/resolvers/deno/npm/mod.rs @@ -1,9 +1,274 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -mod byonm; -mod local; +use std::fmt::Debug; +use std::path::PathBuf; +use std::sync::Arc; + +use boxed_error::Boxed; +use deno_semver::npm::NpmPackageReqReference; +use deno_semver::package::PackageReq; +use node_resolver::env::NodeResolverEnv; +use node_resolver::errors::NodeResolveError; +use node_resolver::errors::NodeResolveErrorKind; +use node_resolver::errors::PackageFolderResolveErrorKind; +use node_resolver::errors::PackageFolderResolveIoError; +use node_resolver::errors::PackageNotFoundError; +use node_resolver::errors::PackageResolveErrorKind; +use node_resolver::errors::PackageSubpathResolveError; +use node_resolver::InNpmPackageChecker; +use node_resolver::NodeModuleKind; +use node_resolver::NodeResolution; +use node_resolver::NodeResolutionMode; +use node_resolver::NodeResolver; +use thiserror::Error; +use url::Url; +use crate::fs::DenoResolverFs; + +pub use byonm::ByonmInNpmPackageChecker; pub use byonm::ByonmNpmResolver; pub use byonm::ByonmNpmResolverCreateOptions; pub use byonm::ByonmResolvePkgFolderFromDenoReqError; pub use local::normalize_pkg_name_for_node_modules_deno_folder; + +mod byonm; +mod local; + +#[derive(Debug, Error)] +#[error("Could not resolve \"{}\", but found it in a package.json. Deno expects the node_modules/ directory to be up to date. Did you forget to run `deno install`?", specifier)] +pub struct NodeModulesOutOfDateError { + pub specifier: String, +} + +#[derive(Debug, Error)] +#[error("Could not find '{}'. Deno expects the node_modules/ directory to be up to date. Did you forget to run `deno install`?", package_json_path.display())] +pub struct MissingPackageNodeModulesFolderError { + pub package_json_path: PathBuf, +} + +#[derive(Debug, Boxed)] +pub struct ResolveIfForNpmPackageError( + pub Box<ResolveIfForNpmPackageErrorKind>, +); + +#[derive(Debug, Error)] +pub enum ResolveIfForNpmPackageErrorKind { + #[error(transparent)] + NodeResolve(#[from] NodeResolveError), + #[error(transparent)] + NodeModulesOutOfDate(#[from] NodeModulesOutOfDateError), +} + +#[derive(Debug, Boxed)] +pub struct ResolveReqWithSubPathError(pub Box<ResolveReqWithSubPathErrorKind>); + +#[derive(Debug, Error)] +pub enum ResolveReqWithSubPathErrorKind { + #[error(transparent)] + MissingPackageNodeModulesFolder(#[from] MissingPackageNodeModulesFolderError), + #[error(transparent)] + ResolvePkgFolderFromDenoReq(#[from] ResolvePkgFolderFromDenoReqError), + #[error(transparent)] + PackageSubpathResolve(#[from] PackageSubpathResolveError), +} + +#[derive(Debug, Error)] +pub enum ResolvePkgFolderFromDenoReqError { + // todo(dsherret): don't use anyhow here + #[error(transparent)] + Managed(anyhow::Error), + #[error(transparent)] + Byonm(#[from] ByonmResolvePkgFolderFromDenoReqError), +} + +// todo(dsherret): a temporary trait until we extract +// out the CLI npm resolver into here +pub trait CliNpmReqResolver: Debug + Send + Sync { + fn resolve_pkg_folder_from_deno_module_req( + &self, + req: &PackageReq, + referrer: &Url, + ) -> Result<PathBuf, ResolvePkgFolderFromDenoReqError>; +} + +pub struct NpmReqResolverOptions< + Fs: DenoResolverFs, + TNodeResolverEnv: NodeResolverEnv, +> { + /// The resolver when "bring your own node_modules" is enabled where Deno + /// does not setup the node_modules directories automatically, but instead + /// uses what already exists on the file system. + pub byonm_resolver: Option<Arc<ByonmNpmResolver<Fs, TNodeResolverEnv>>>, + pub fs: Fs, + pub in_npm_pkg_checker: Arc<dyn InNpmPackageChecker>, + pub node_resolver: Arc<NodeResolver<TNodeResolverEnv>>, + pub npm_req_resolver: Arc<dyn CliNpmReqResolver>, +} + +#[derive(Debug)] +pub struct NpmReqResolver<Fs: DenoResolverFs, TNodeResolverEnv: NodeResolverEnv> +{ + byonm_resolver: Option<Arc<ByonmNpmResolver<Fs, TNodeResolverEnv>>>, + fs: Fs, + in_npm_pkg_checker: Arc<dyn InNpmPackageChecker>, + node_resolver: Arc<NodeResolver<TNodeResolverEnv>>, + npm_resolver: Arc<dyn CliNpmReqResolver>, +} + +impl<Fs: DenoResolverFs, TNodeResolverEnv: NodeResolverEnv> + NpmReqResolver<Fs, TNodeResolverEnv> +{ + pub fn new(options: NpmReqResolverOptions<Fs, TNodeResolverEnv>) -> Self { + Self { + byonm_resolver: options.byonm_resolver, + fs: options.fs, + in_npm_pkg_checker: options.in_npm_pkg_checker, + node_resolver: options.node_resolver, + npm_resolver: options.npm_req_resolver, + } + } + + pub fn resolve_req_reference( + &self, + req_ref: &NpmPackageReqReference, + referrer: &Url, + referrer_kind: NodeModuleKind, + mode: NodeResolutionMode, + ) -> Result<Url, ResolveReqWithSubPathError> { + self.resolve_req_with_sub_path( + req_ref.req(), + req_ref.sub_path(), + referrer, + referrer_kind, + mode, + ) + } + + pub fn resolve_req_with_sub_path( + &self, + req: &PackageReq, + sub_path: Option<&str>, + referrer: &Url, + referrer_kind: NodeModuleKind, + mode: NodeResolutionMode, + ) -> Result<Url, ResolveReqWithSubPathError> { + let package_folder = self + .npm_resolver + .resolve_pkg_folder_from_deno_module_req(req, referrer)?; + let resolution_result = + self.node_resolver.resolve_package_subpath_from_deno_module( + &package_folder, + sub_path, + Some(referrer), + referrer_kind, + mode, + ); + match resolution_result { + Ok(url) => Ok(url), + Err(err) => { + if self.byonm_resolver.is_some() { + let package_json_path = package_folder.join("package.json"); + if !self.fs.exists_sync(&package_json_path) { + return Err( + MissingPackageNodeModulesFolderError { package_json_path }.into(), + ); + } + } + Err(err.into()) + } + } + } + + pub fn resolve_if_for_npm_pkg( + &self, + specifier: &str, + referrer: &Url, + referrer_kind: NodeModuleKind, + mode: NodeResolutionMode, + ) -> Result<Option<NodeResolution>, ResolveIfForNpmPackageError> { + let resolution_result = + self + .node_resolver + .resolve(specifier, referrer, referrer_kind, mode); + match resolution_result { + Ok(res) => Ok(Some(res)), + Err(err) => { + let err = err.into_kind(); + match err { + NodeResolveErrorKind::RelativeJoin(_) + | NodeResolveErrorKind::PackageImportsResolve(_) + | NodeResolveErrorKind::UnsupportedEsmUrlScheme(_) + | NodeResolveErrorKind::DataUrlReferrer(_) + | NodeResolveErrorKind::TypesNotFound(_) + | NodeResolveErrorKind::FinalizeResolution(_) => Err( + ResolveIfForNpmPackageErrorKind::NodeResolve(err.into()).into_box(), + ), + NodeResolveErrorKind::PackageResolve(err) => { + let err = err.into_kind(); + match err { + PackageResolveErrorKind::ClosestPkgJson(_) + | PackageResolveErrorKind::InvalidModuleSpecifier(_) + | PackageResolveErrorKind::ExportsResolve(_) + | PackageResolveErrorKind::SubpathResolve(_) => Err( + ResolveIfForNpmPackageErrorKind::NodeResolve( + NodeResolveErrorKind::PackageResolve(err.into()).into(), + ) + .into_box(), + ), + PackageResolveErrorKind::PackageFolderResolve(err) => { + match err.as_kind() { + PackageFolderResolveErrorKind::Io( + PackageFolderResolveIoError { package_name, .. }, + ) + | PackageFolderResolveErrorKind::PackageNotFound( + PackageNotFoundError { package_name, .. }, + ) => { + if self.in_npm_pkg_checker.in_npm_package(referrer) { + return Err( + ResolveIfForNpmPackageErrorKind::NodeResolve( + NodeResolveErrorKind::PackageResolve(err.into()) + .into(), + ) + .into_box(), + ); + } + if let Some(byonm_npm_resolver) = &self.byonm_resolver { + if byonm_npm_resolver + .find_ancestor_package_json_with_dep( + package_name, + referrer, + ) + .is_some() + { + return Err( + ResolveIfForNpmPackageErrorKind::NodeModulesOutOfDate( + NodeModulesOutOfDateError { + specifier: specifier.to_string(), + }, + ).into_box(), + ); + } + } + Ok(None) + } + PackageFolderResolveErrorKind::ReferrerNotFound(_) => { + if self.in_npm_pkg_checker.in_npm_package(referrer) { + return Err( + ResolveIfForNpmPackageErrorKind::NodeResolve( + NodeResolveErrorKind::PackageResolve(err.into()) + .into(), + ) + .into_box(), + ); + } + Ok(None) + } + } + } + } + } + } + } + } + } +} |