summaryrefslogtreecommitdiff
path: root/resolvers/deno/npm/mod.rs
diff options
context:
space:
mode:
Diffstat (limited to 'resolvers/deno/npm/mod.rs')
-rw-r--r--resolvers/deno/npm/mod.rs269
1 files changed, 267 insertions, 2 deletions
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)
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}