0
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2025-02-20 20:33:42 -05:00
denoland-deno/resolvers/deno/npm/mod.rs
David Sherret 273ec9fbf2
refactor: add WorkspaceFactory and ResolverFactory (#27766)
Allows easily constructing a `DenoResolver` using the exact same logic
that we use in the CLI (useful for dnt and for external bundlers). This
code is then used in the CLI to ensure the logic is always up-to-date.

```rs
use std::rc::Rc;

use deno_resolver:🏭:ResolverFactory;
use deno_resolver:🏭:WorkspaceFactory;
use sys_traits::impls::RealSys;

let sys = RealSys;
let cwd = sys.env_current_dir()?;
let workspace_factory = Rc::new(WorkspaceFactory::new(sys, cwd, Default::default()));
let resolver_factory = ResolverFactory::new(workspace_factory.clone(), Default::default());
let deno_resolver = resolver_factory.deno_resolver().await?;
```
2025-01-23 18:52:55 -05:00

475 lines
14 KiB
Rust

// Copyright 2018-2025 the Deno authors. MIT license.
use std::fmt::Debug;
use std::path::Path;
use std::path::PathBuf;
use boxed_error::Boxed;
use deno_error::JsError;
use deno_semver::npm::NpmPackageReqReference;
use deno_semver::package::PackageReq;
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::IsBuiltInNodeModuleChecker;
use node_resolver::NodeResolution;
use node_resolver::NodeResolutionKind;
use node_resolver::NodeResolverRc;
use node_resolver::NpmPackageFolderResolver;
use node_resolver::ResolutionMode;
use sys_traits::FsCanonicalize;
use sys_traits::FsMetadata;
use sys_traits::FsRead;
use sys_traits::FsReadDir;
use thiserror::Error;
use url::Url;
pub use self::byonm::ByonmInNpmPackageChecker;
pub use self::byonm::ByonmNpmResolver;
pub use self::byonm::ByonmNpmResolverCreateOptions;
pub use self::byonm::ByonmNpmResolverRc;
pub use self::byonm::ByonmResolvePkgFolderFromDenoReqError;
pub use self::local::get_package_folder_id_folder_name;
pub use self::local::normalize_pkg_name_for_node_modules_deno_folder;
use self::managed::create_managed_in_npm_pkg_checker;
use self::managed::ManagedInNpmPackageChecker;
use self::managed::ManagedInNpmPkgCheckerCreateOptions;
pub use self::managed::ManagedNpmResolver;
use self::managed::ManagedNpmResolverCreateOptions;
pub use self::managed::ManagedNpmResolverRc;
use crate::sync::new_rc;
use crate::sync::MaybeSend;
use crate::sync::MaybeSync;
mod byonm;
mod local;
pub mod managed;
#[derive(Debug)]
pub enum CreateInNpmPkgCheckerOptions<'a> {
Managed(ManagedInNpmPkgCheckerCreateOptions<'a>),
Byonm,
}
#[derive(Debug, Clone)]
pub enum DenoInNpmPackageChecker {
Managed(ManagedInNpmPackageChecker),
Byonm(ByonmInNpmPackageChecker),
}
impl DenoInNpmPackageChecker {
pub fn new(options: CreateInNpmPkgCheckerOptions) -> Self {
match options {
CreateInNpmPkgCheckerOptions::Managed(options) => {
DenoInNpmPackageChecker::Managed(create_managed_in_npm_pkg_checker(
options,
))
}
CreateInNpmPkgCheckerOptions::Byonm => {
DenoInNpmPackageChecker::Byonm(ByonmInNpmPackageChecker)
}
}
}
}
impl InNpmPackageChecker for DenoInNpmPackageChecker {
fn in_npm_package(&self, specifier: &Url) -> bool {
match self {
DenoInNpmPackageChecker::Managed(c) => c.in_npm_package(specifier),
DenoInNpmPackageChecker::Byonm(c) => c.in_npm_package(specifier),
}
}
}
#[derive(Debug, Error, JsError)]
#[class(generic)]
#[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, JsError)]
#[class(generic)]
#[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, JsError)]
pub struct ResolveIfForNpmPackageError(
pub Box<ResolveIfForNpmPackageErrorKind>,
);
#[derive(Debug, Error, JsError)]
pub enum ResolveIfForNpmPackageErrorKind {
#[class(inherit)]
#[error(transparent)]
NodeResolve(#[from] NodeResolveError),
#[class(inherit)]
#[error(transparent)]
NodeModulesOutOfDate(#[from] NodeModulesOutOfDateError),
}
#[derive(Debug, Boxed, JsError)]
pub struct ResolveReqWithSubPathError(pub Box<ResolveReqWithSubPathErrorKind>);
#[derive(Debug, Error, JsError)]
pub enum ResolveReqWithSubPathErrorKind {
#[class(inherit)]
#[error(transparent)]
MissingPackageNodeModulesFolder(#[from] MissingPackageNodeModulesFolderError),
#[class(inherit)]
#[error(transparent)]
ResolvePkgFolderFromDenoReq(#[from] ResolvePkgFolderFromDenoReqError),
#[class(inherit)]
#[error(transparent)]
PackageSubpathResolve(#[from] PackageSubpathResolveError),
}
#[derive(Debug, Error, JsError)]
pub enum ResolvePkgFolderFromDenoReqError {
#[class(inherit)]
#[error(transparent)]
Managed(managed::ManagedResolvePkgFolderFromDenoReqError),
#[class(inherit)]
#[error(transparent)]
Byonm(byonm::ByonmResolvePkgFolderFromDenoReqError),
}
pub enum NpmResolverCreateOptions<
TSys: FsRead
+ FsCanonicalize
+ FsMetadata
+ std::fmt::Debug
+ MaybeSend
+ MaybeSync
+ Clone
+ 'static,
> {
Managed(ManagedNpmResolverCreateOptions<TSys>),
Byonm(ByonmNpmResolverCreateOptions<TSys>),
}
#[derive(Debug, Clone)]
pub enum NpmResolver<TSys: FsCanonicalize + FsMetadata + FsRead + FsReadDir> {
/// 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.
Byonm(ByonmNpmResolverRc<TSys>),
Managed(ManagedNpmResolverRc<TSys>),
}
impl<TSys: FsCanonicalize + FsMetadata + FsRead + FsReadDir> NpmResolver<TSys> {
pub fn new<
TCreateSys: FsCanonicalize
+ FsMetadata
+ FsRead
+ FsReadDir
+ std::fmt::Debug
+ MaybeSend
+ MaybeSync
+ Clone
+ 'static,
>(
options: NpmResolverCreateOptions<TCreateSys>,
) -> NpmResolver<TCreateSys> {
match options {
NpmResolverCreateOptions::Managed(options) => {
NpmResolver::Managed(new_rc(ManagedNpmResolver::<TCreateSys>::new::<
TCreateSys,
>(options)))
}
NpmResolverCreateOptions::Byonm(options) => {
NpmResolver::Byonm(new_rc(ByonmNpmResolver::new(options)))
}
}
}
pub fn is_byonm(&self) -> bool {
matches!(self, NpmResolver::Byonm(_))
}
pub fn is_managed(&self) -> bool {
matches!(self, NpmResolver::Managed(_))
}
pub fn as_managed(&self) -> Option<&ManagedNpmResolver<TSys>> {
match self {
NpmResolver::Managed(resolver) => Some(resolver),
NpmResolver::Byonm(_) => None,
}
}
pub fn root_node_modules_path(&self) -> Option<&Path> {
match self {
NpmResolver::Byonm(resolver) => resolver.root_node_modules_path(),
NpmResolver::Managed(resolver) => resolver.root_node_modules_path(),
}
}
pub fn resolve_pkg_folder_from_deno_module_req(
&self,
req: &PackageReq,
referrer: &Url,
) -> Result<PathBuf, ResolvePkgFolderFromDenoReqError> {
match self {
NpmResolver::Byonm(byonm_resolver) => byonm_resolver
.resolve_pkg_folder_from_deno_module_req(req, referrer)
.map_err(ResolvePkgFolderFromDenoReqError::Byonm),
NpmResolver::Managed(managed_resolver) => managed_resolver
.resolve_pkg_folder_from_deno_module_req(req, referrer)
.map_err(ResolvePkgFolderFromDenoReqError::Managed),
}
}
}
impl<TSys: FsCanonicalize + FsMetadata + FsRead + FsReadDir>
NpmPackageFolderResolver for NpmResolver<TSys>
{
fn resolve_package_folder_from_package(
&self,
specifier: &str,
referrer: &Url,
) -> Result<PathBuf, node_resolver::errors::PackageFolderResolveError> {
match self {
NpmResolver::Byonm(byonm_resolver) => {
byonm_resolver.resolve_package_folder_from_package(specifier, referrer)
}
NpmResolver::Managed(managed_resolver) => managed_resolver
.resolve_package_folder_from_package(specifier, referrer),
}
}
}
pub struct NpmReqResolverOptions<
TInNpmPackageChecker: InNpmPackageChecker,
TIsBuiltInNodeModuleChecker: IsBuiltInNodeModuleChecker,
TNpmPackageFolderResolver: NpmPackageFolderResolver,
TSys: FsCanonicalize + FsMetadata + FsRead + FsReadDir,
> {
pub in_npm_pkg_checker: TInNpmPackageChecker,
pub node_resolver: NodeResolverRc<
TInNpmPackageChecker,
TIsBuiltInNodeModuleChecker,
TNpmPackageFolderResolver,
TSys,
>,
pub npm_resolver: NpmResolver<TSys>,
pub sys: TSys,
}
#[allow(clippy::disallowed_types)]
pub type NpmReqResolverRc<
TInNpmPackageChecker,
TIsBuiltInNodeModuleChecker,
TNpmPackageFolderResolver,
TSys,
> = crate::sync::MaybeArc<
NpmReqResolver<
TInNpmPackageChecker,
TIsBuiltInNodeModuleChecker,
TNpmPackageFolderResolver,
TSys,
>,
>;
#[derive(Debug)]
pub struct NpmReqResolver<
TInNpmPackageChecker: InNpmPackageChecker,
TIsBuiltInNodeModuleChecker: IsBuiltInNodeModuleChecker,
TNpmPackageFolderResolver: NpmPackageFolderResolver,
TSys: FsCanonicalize + FsMetadata + FsRead + FsReadDir,
> {
sys: TSys,
in_npm_pkg_checker: TInNpmPackageChecker,
node_resolver: NodeResolverRc<
TInNpmPackageChecker,
TIsBuiltInNodeModuleChecker,
TNpmPackageFolderResolver,
TSys,
>,
npm_resolver: NpmResolver<TSys>,
}
impl<
TInNpmPackageChecker: InNpmPackageChecker,
TIsBuiltInNodeModuleChecker: IsBuiltInNodeModuleChecker,
TNpmPackageFolderResolver: NpmPackageFolderResolver,
TSys: FsCanonicalize + FsMetadata + FsRead + FsReadDir,
>
NpmReqResolver<
TInNpmPackageChecker,
TIsBuiltInNodeModuleChecker,
TNpmPackageFolderResolver,
TSys,
>
{
pub fn new(
options: NpmReqResolverOptions<
TInNpmPackageChecker,
TIsBuiltInNodeModuleChecker,
TNpmPackageFolderResolver,
TSys,
>,
) -> Self {
Self {
sys: options.sys,
in_npm_pkg_checker: options.in_npm_pkg_checker,
node_resolver: options.node_resolver,
npm_resolver: options.npm_resolver,
}
}
pub fn resolve_req_reference(
&self,
req_ref: &NpmPackageReqReference,
referrer: &Url,
resolution_mode: ResolutionMode,
resolution_kind: NodeResolutionKind,
) -> Result<Url, ResolveReqWithSubPathError> {
self.resolve_req_with_sub_path(
req_ref.req(),
req_ref.sub_path(),
referrer,
resolution_mode,
resolution_kind,
)
}
pub fn resolve_req_with_sub_path(
&self,
req: &PackageReq,
sub_path: Option<&str>,
referrer: &Url,
resolution_mode: ResolutionMode,
resolution_kind: NodeResolutionKind,
) -> 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),
resolution_mode,
resolution_kind,
);
match resolution_result {
Ok(url) => Ok(url),
Err(err) => {
if matches!(self.npm_resolver, NpmResolver::Byonm(_)) {
let package_json_path = package_folder.join("package.json");
if !self.sys.fs_exists_no_err(&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,
resolution_mode: ResolutionMode,
resolution_kind: NodeResolutionKind,
) -> Result<Option<NodeResolution>, ResolveIfForNpmPackageError> {
let resolution_result = self.node_resolver.resolve(
specifier,
referrer,
resolution_mode,
resolution_kind,
);
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 NpmResolver::Byonm(byonm_npm_resolver) =
&self.npm_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)
}
}
}
}
}
}
}
}
}
}