mod common;
mod global;
mod local;
mod resolution;
use std::borrow::Cow;
use std::path::Path;
use std::path::PathBuf;
use deno_npm::NpmPackageCacheFolderId;
use deno_npm::NpmPackageId;
use deno_npm::NpmSystemInfo;
use deno_npm::resolution::PackageCacheFolderIdNotFoundError;
use deno_npm::resolution::PackageNvNotFoundError;
use deno_npm::resolution::PackageReqNotFoundError;
use deno_path_util::fs::canonicalize_path_maybe_not_exists;
use deno_semver::Version;
use deno_semver::package::PackageReq;
use node_resolver::InNpmPackageChecker;
use node_resolver::NpmPackageFolderResolver;
use node_resolver::UrlOrPathRef;
use node_resolver::cache::NodeResolutionSys;
use url::Url;
use self::common::NpmPackageFsResolver;
use self::global::GlobalNpmPackageResolver;
use self::global::GlobalNpmPackageResolverSys;
use self::local::LocalNpmPackageResolver;
pub use self::resolution::NpmResolutionCell;
pub use self::resolution::NpmResolutionCellRc;
use crate::NpmCacheDirRc;
use crate::npmrc::ResolvedNpmRcRc;
#[derive(Debug, thiserror::Error, deno_error::JsError)]
pub enum ResolvePkgFolderFromDenoModuleError {
#[class(inherit)]
#[error(transparent)]
PackageNvNotFound(#[from] PackageNvNotFoundError),
#[class(inherit)]
#[error(transparent)]
ResolvePkgFolderFromPkgId(#[from] ResolvePkgFolderFromPkgIdError),
}
#[derive(Debug, thiserror::Error, deno_error::JsError)]
#[error(transparent)]
pub enum ResolvePkgFolderFromPkgIdError {
#[class(inherit)]
#[error(transparent)]
NotFound(#[from] NpmManagedPackageFolderNotFoundError),
#[class(inherit)]
#[error(transparent)]
FailedCanonicalizing(#[from] FailedCanonicalizingError),
}
#[derive(Debug, thiserror::Error, deno_error::JsError)]
#[class(generic)]
#[error("Package folder not found for '{0}'")]
pub struct NpmManagedPackageFolderNotFoundError(deno_semver::StackString);
#[derive(Debug, thiserror::Error, deno_error::JsError)]
#[class(generic)]
#[error("Failed canonicalizing '{}'", path.display())]
pub struct FailedCanonicalizingError {
path: PathBuf,
#[source]
source: std::io::Error,
}
#[derive(Debug, thiserror::Error, deno_error::JsError)]
pub enum ManagedResolvePkgFolderFromDenoReqError {
#[class(inherit)]
#[error(transparent)]
PackageReqNotFound(#[from] PackageReqNotFoundError),
#[class(inherit)]
#[error(transparent)]
ResolvePkgFolderFromPkgId(#[from] ResolvePkgFolderFromPkgIdError),
}
#[derive(Debug, thiserror::Error, deno_error::JsError)]
pub enum ResolvePkgIdFromSpecifierError {
#[class(inherit)]
#[error(transparent)]
Io(#[from] std::io::Error),
#[class(inherit)]
#[error(transparent)]
NotFound(#[from] PackageCacheFolderIdNotFoundError),
}
pub struct ManagedNpmResolverCreateOptions<TSys: ManagedNpmResolverSys> {
pub npm_cache_dir: NpmCacheDirRc,
pub sys: NodeResolutionSys<TSys>,
pub maybe_node_modules_path: Option<PathBuf>,
pub npm_system_info: NpmSystemInfo,
pub npmrc: ResolvedNpmRcRc,
pub npm_resolution: NpmResolutionCellRc,
}
#[sys_traits::auto_impl]
pub trait ManagedNpmResolverSys: GlobalNpmPackageResolverSys + Clone {}
#[allow(clippy::disallowed_types, reason = "definition")]
pub type ManagedNpmResolverRc<TSys> =
deno_maybe_sync::MaybeArc<ManagedNpmResolver<TSys>>;
#[derive(Debug)]
pub struct ManagedNpmResolver<TSys: ManagedNpmResolverSys> {
fs_resolver: NpmPackageFsResolver<TSys>,
npm_cache_dir: NpmCacheDirRc,
resolution: NpmResolutionCellRc,
sys: NodeResolutionSys<TSys>,
}
impl<TSys: ManagedNpmResolverSys> ManagedNpmResolver<TSys> {
pub fn new<TCreateSys: ManagedNpmResolverSys>(
options: ManagedNpmResolverCreateOptions<TCreateSys>,
) -> ManagedNpmResolver<TCreateSys> {
let fs_resolver = match options.maybe_node_modules_path {
Some(node_modules_folder) => {
NpmPackageFsResolver::Local(LocalNpmPackageResolver::new(
options.npm_resolution.clone(),
options.sys.clone(),
node_modules_folder,
))
}
None => NpmPackageFsResolver::Global(GlobalNpmPackageResolver::new(
options.npm_cache_dir.clone(),
options.npmrc.clone(),
options.npm_resolution.clone(),
options.sys.clone(),
)),
};
ManagedNpmResolver {
fs_resolver,
npm_cache_dir: options.npm_cache_dir,
sys: options.sys,
resolution: options.npm_resolution,
}
}
#[inline]
pub fn root_node_modules_path(&self) -> Option<&Path> {
self.fs_resolver.node_modules_path()
}
pub fn global_cache_root_path(&self) -> &Path {
self.npm_cache_dir.root_dir()
}
pub fn global_cache_root_url(&self) -> &Url {
self.npm_cache_dir.root_dir_url()
}
pub fn resolution(&self) -> &NpmResolutionCell {
self.resolution.as_ref()
}
pub fn is_pkg_req_folder_cached(&self, req: &PackageReq) -> bool {
self
.resolution
.resolve_pkg_id_from_pkg_req(req)
.ok()
.and_then(|id| self.resolve_pkg_folder_from_pkg_id(&id).ok())
.map(|folder| self.sys.exists_(Cow::Owned(folder)))
.unwrap_or(false)
}
pub fn resolve_pkg_folder_from_pkg_id(
&self,
package_id: &NpmPackageId,
) -> Result<PathBuf, ResolvePkgFolderFromPkgIdError> {
let path = self
.fs_resolver
.maybe_package_folder(package_id)
.ok_or_else(|| {
NpmManagedPackageFolderNotFoundError(package_id.as_serialized())
})?;
let path = canonicalize_path_maybe_not_exists(&self.sys, &path).map_err(
|source| FailedCanonicalizingError {
path: path.to_path_buf(),
source,
},
)?;
log::debug!(
"Resolved package folder of {} to {}",
package_id.as_serialized(),
path.display()
);
Ok(path)
}
pub fn resolve_pkg_id_from_deno_module_req(
&self,
req: &PackageReq,
) -> Result<NpmPackageId, PackageReqNotFoundError> {
self.resolution.resolve_pkg_id_from_pkg_req(req)
}
pub fn resolve_pkg_folder_from_deno_module_req(
&self,
req: &PackageReq,
) -> Result<PathBuf, ManagedResolvePkgFolderFromDenoReqError> {
let pkg_id = self.resolution.resolve_pkg_id_from_pkg_req(req)?;
Ok(self.resolve_pkg_folder_from_pkg_id(&pkg_id)?)
}
#[inline]
pub fn resolve_package_cache_folder_id_from_specifier(
&self,
specifier: &Url,
) -> Result<Option<NpmPackageCacheFolderId>, std::io::Error> {
self
.fs_resolver
.resolve_package_cache_folder_id_from_specifier(specifier)
}
pub fn resolve_pkg_id_from_specifier(
&self,
specifier: &Url,
) -> Result<Option<NpmPackageId>, ResolvePkgIdFromSpecifierError> {
let Some(cache_folder_id) = self
.fs_resolver
.resolve_package_cache_folder_id_from_specifier(specifier)?
else {
return Ok(None);
};
Ok(Some(
self
.resolution
.resolve_pkg_id_from_pkg_cache_folder_id(&cache_folder_id)?,
))
}
}
impl<TSys: ManagedNpmResolverSys> NpmPackageFolderResolver
for ManagedNpmResolver<TSys>
{
fn resolve_package_folder_from_package(
&self,
specifier: &str,
referrer: &UrlOrPathRef,
) -> Result<PathBuf, node_resolver::errors::PackageFolderResolveError> {
let path = self
.fs_resolver
.resolve_package_folder_from_package(specifier, referrer)?;
log::debug!(
"Resolved {} from {} to {}",
specifier,
referrer.display(),
path.display()
);
Ok(path)
}
fn resolve_types_package_folder(
&self,
types_package_name: &str,
maybe_package_version: Option<&Version>,
maybe_referrer: Option<&UrlOrPathRef>,
) -> Option<PathBuf> {
self.fs_resolver.resolve_types_package_folder(
types_package_name,
maybe_package_version,
maybe_referrer,
)
}
}
#[derive(Debug, Clone)]
pub struct ManagedInNpmPackageChecker {
root_dir: Url,
}
impl InNpmPackageChecker for ManagedInNpmPackageChecker {
fn in_npm_package(&self, specifier: &Url) -> bool {
specifier.as_ref().starts_with(self.root_dir.as_str())
}
}
#[derive(Debug)]
pub struct ManagedInNpmPkgCheckerCreateOptions<'a> {
pub root_cache_dir_url: &'a Url,
pub maybe_node_modules_path: Option<&'a Path>,
}
pub fn create_managed_in_npm_pkg_checker(
options: ManagedInNpmPkgCheckerCreateOptions,
) -> ManagedInNpmPackageChecker {
let root_dir = match options.maybe_node_modules_path {
Some(node_modules_folder) => {
deno_path_util::url_from_directory_path(node_modules_folder).unwrap()
}
None => options.root_cache_dir_url.clone(),
};
debug_assert!(root_dir.as_str().ends_with('/'));
ManagedInNpmPackageChecker { root_dir }
}