mirror of
https://github.com/denoland/deno.git
synced 2024-12-18 13:22:55 -05:00
115a306656
Ensures a dynamic import in a CJS file will consider the referrer as an import for node resolution. Also adds fixes (adds) support for `"resolution-mode"` in TypeScript.
276 lines
9.2 KiB
Rust
276 lines
9.2 KiB
Rust
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
|
|
|
|
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::NodeResolution;
|
|
use node_resolver::NodeResolutionKind;
|
|
use node_resolver::NodeResolver;
|
|
use node_resolver::ResolutionMode;
|
|
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,
|
|
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 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,
|
|
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 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)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|