summaryrefslogtreecommitdiff
path: root/ext/node
diff options
context:
space:
mode:
authorDavid Sherret <dsherret@users.noreply.github.com>2024-07-25 19:08:14 -0400
committerGitHub <noreply@github.com>2024-07-25 19:08:14 -0400
commit3bf147fe287ac779b20d318daba56b336f356adf (patch)
tree3b5bfe2a1ad918b275a2cd08f7dcc05f90a180ab /ext/node
parent0cf7f268a7df7711ac6ab8c2c67b4d7abf454fcd (diff)
refactor: decouple node resolution from deno_core (#24724)
Diffstat (limited to 'ext/node')
-rw-r--r--ext/node/Cargo.toml3
-rw-r--r--ext/node/analyze.rs619
-rw-r--r--ext/node/errors.rs769
-rw-r--r--ext/node/global.rs2
-rw-r--r--ext/node/lib.rs161
-rw-r--r--ext/node/ops/require.rs17
-rw-r--r--ext/node/ops/worker_threads.rs14
-rw-r--r--ext/node/package_json.rs70
-rw-r--r--ext/node/path.rs50
-rw-r--r--ext/node/polyfill.rs14
-rw-r--r--ext/node/resolution.rs2016
11 files changed, 126 insertions, 3609 deletions
diff --git a/ext/node/Cargo.toml b/ext/node/Cargo.toml
index ed168eace..00afb64eb 100644
--- a/ext/node/Cargo.toml
+++ b/ext/node/Cargo.toml
@@ -14,7 +14,7 @@ description = "Node compatibility for Deno"
path = "lib.rs"
[features]
-sync_fs = ["deno_package_json/sync"]
+sync_fs = ["deno_package_json/sync", "node_resolver/sync"]
[dependencies]
aead-gcm-stream = "0.1"
@@ -55,6 +55,7 @@ libc.workspace = true
libz-sys.workspace = true
md-5 = { version = "0.10.5", features = ["oid"] }
md4 = "0.10.2"
+node_resolver.workspace = true
num-bigint.workspace = true
num-bigint-dig = "0.8.2"
num-integer = "0.1.45"
diff --git a/ext/node/analyze.rs b/ext/node/analyze.rs
deleted file mode 100644
index 3513a8105..000000000
--- a/ext/node/analyze.rs
+++ /dev/null
@@ -1,619 +0,0 @@
-// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
-
-use std::collections::BTreeSet;
-use std::collections::HashSet;
-use std::path::Path;
-use std::path::PathBuf;
-
-use deno_core::anyhow;
-use deno_core::anyhow::Context;
-use deno_core::futures::future::LocalBoxFuture;
-use deno_core::futures::stream::FuturesUnordered;
-use deno_core::futures::FutureExt;
-use deno_core::futures::StreamExt;
-use deno_core::ModuleSpecifier;
-use once_cell::sync::Lazy;
-
-use deno_core::error::AnyError;
-
-use crate::package_json::load_pkg_json;
-use crate::path::to_file_specifier;
-use crate::resolution::NodeResolverRc;
-use crate::NodeModuleKind;
-use crate::NodeResolutionMode;
-use crate::NpmResolverRc;
-use crate::PathClean;
-
-#[derive(Debug, Clone)]
-pub enum CjsAnalysis {
- /// File was found to be an ES module and the translator should
- /// load the code as ESM.
- Esm(String),
- Cjs(CjsAnalysisExports),
-}
-
-#[derive(Debug, Clone)]
-pub struct CjsAnalysisExports {
- pub exports: Vec<String>,
- pub reexports: Vec<String>,
-}
-
-/// Code analyzer for CJS and ESM files.
-#[async_trait::async_trait(?Send)]
-pub trait CjsCodeAnalyzer {
- /// Analyzes CommonJs code for exports and reexports, which is
- /// then used to determine the wrapper ESM module exports.
- ///
- /// Note that the source is provided by the caller when the caller
- /// already has it. If the source is needed by the implementation,
- /// then it can use the provided source, or otherwise load it if
- /// necessary.
- async fn analyze_cjs(
- &self,
- specifier: &ModuleSpecifier,
- maybe_source: Option<String>,
- ) -> Result<CjsAnalysis, AnyError>;
-}
-
-pub struct NodeCodeTranslator<TCjsCodeAnalyzer: CjsCodeAnalyzer> {
- cjs_code_analyzer: TCjsCodeAnalyzer,
- fs: deno_fs::FileSystemRc,
- node_resolver: NodeResolverRc,
- npm_resolver: NpmResolverRc,
-}
-
-impl<TCjsCodeAnalyzer: CjsCodeAnalyzer> NodeCodeTranslator<TCjsCodeAnalyzer> {
- pub fn new(
- cjs_code_analyzer: TCjsCodeAnalyzer,
- fs: deno_fs::FileSystemRc,
- node_resolver: NodeResolverRc,
- npm_resolver: NpmResolverRc,
- ) -> Self {
- Self {
- cjs_code_analyzer,
- fs,
- node_resolver,
- npm_resolver,
- }
- }
-
- /// Translates given CJS module into ESM. This function will perform static
- /// analysis on the file to find defined exports and reexports.
- ///
- /// For all discovered reexports the analysis will be performed recursively.
- ///
- /// If successful a source code for equivalent ES module is returned.
- pub async fn translate_cjs_to_esm(
- &self,
- entry_specifier: &ModuleSpecifier,
- source: Option<String>,
- ) -> Result<String, AnyError> {
- let mut temp_var_count = 0;
-
- let analysis = self
- .cjs_code_analyzer
- .analyze_cjs(entry_specifier, source)
- .await?;
-
- let analysis = match analysis {
- CjsAnalysis::Esm(source) => return Ok(source),
- CjsAnalysis::Cjs(analysis) => analysis,
- };
-
- let mut source = vec![
- r#"import {createRequire as __internalCreateRequire} from "node:module";
- const require = __internalCreateRequire(import.meta.url);"#
- .to_string(),
- ];
-
- // use a BTreeSet to make the output deterministic for v8's code cache
- let mut all_exports = analysis.exports.into_iter().collect::<BTreeSet<_>>();
-
- if !analysis.reexports.is_empty() {
- let mut errors = Vec::new();
- self
- .analyze_reexports(
- entry_specifier,
- analysis.reexports,
- &mut all_exports,
- &mut errors,
- )
- .await;
-
- // surface errors afterwards in a deterministic way
- if !errors.is_empty() {
- errors.sort_by_cached_key(|e| e.to_string());
- return Err(errors.remove(0));
- }
- }
-
- source.push(format!(
- "const mod = require(\"{}\");",
- entry_specifier
- .to_file_path()
- .unwrap()
- .to_str()
- .unwrap()
- .replace('\\', "\\\\")
- .replace('\'', "\\\'")
- .replace('\"', "\\\"")
- ));
-
- for export in &all_exports {
- if export.as_str() != "default" {
- add_export(
- &mut source,
- export,
- &format!("mod[\"{}\"]", escape_for_double_quote_string(export)),
- &mut temp_var_count,
- );
- }
- }
-
- source.push("export default mod;".to_string());
-
- let translated_source = source.join("\n");
- Ok(translated_source)
- }
-
- async fn analyze_reexports<'a>(
- &'a self,
- entry_specifier: &url::Url,
- reexports: Vec<String>,
- all_exports: &mut BTreeSet<String>,
- // this goes through the modules concurrently, so collect
- // the errors in order to be deterministic
- errors: &mut Vec<anyhow::Error>,
- ) {
- struct Analysis {
- reexport_specifier: url::Url,
- referrer: url::Url,
- analysis: CjsAnalysis,
- }
-
- type AnalysisFuture<'a> = LocalBoxFuture<'a, Result<Analysis, AnyError>>;
-
- let mut handled_reexports: HashSet<ModuleSpecifier> = HashSet::default();
- handled_reexports.insert(entry_specifier.clone());
- let mut analyze_futures: FuturesUnordered<AnalysisFuture<'a>> =
- FuturesUnordered::new();
- let cjs_code_analyzer = &self.cjs_code_analyzer;
- let mut handle_reexports =
- |referrer: url::Url,
- reexports: Vec<String>,
- analyze_futures: &mut FuturesUnordered<AnalysisFuture<'a>>,
- errors: &mut Vec<anyhow::Error>| {
- // 1. Resolve the re-exports and start a future to analyze each one
- for reexport in reexports {
- let result = self.resolve(
- &reexport,
- &referrer,
- // FIXME(bartlomieju): check if these conditions are okay, probably
- // should be `deno-require`, because `deno` is already used in `esm_resolver.rs`
- &["deno", "require", "default"],
- NodeResolutionMode::Execution,
- );
- let reexport_specifier = match result {
- Ok(specifier) => specifier,
- Err(err) => {
- errors.push(err);
- continue;
- }
- };
-
- if !handled_reexports.insert(reexport_specifier.clone()) {
- continue;
- }
-
- let referrer = referrer.clone();
- let future = async move {
- let analysis = cjs_code_analyzer
- .analyze_cjs(&reexport_specifier, None)
- .await
- .with_context(|| {
- format!(
- "Could not load '{}' ({}) referenced from {}",
- reexport, reexport_specifier, referrer
- )
- })?;
-
- Ok(Analysis {
- reexport_specifier,
- referrer,
- analysis,
- })
- }
- .boxed_local();
- analyze_futures.push(future);
- }
- };
-
- handle_reexports(
- entry_specifier.clone(),
- reexports,
- &mut analyze_futures,
- errors,
- );
-
- while let Some(analysis_result) = analyze_futures.next().await {
- // 2. Look at the analysis result and resolve its exports and re-exports
- let Analysis {
- reexport_specifier,
- referrer,
- analysis,
- } = match analysis_result {
- Ok(analysis) => analysis,
- Err(err) => {
- errors.push(err);
- continue;
- }
- };
- match analysis {
- CjsAnalysis::Esm(_) => {
- // todo(dsherret): support this once supporting requiring ES modules
- errors.push(anyhow::anyhow!(
- "Cannot require ES module '{}' from '{}'",
- reexport_specifier,
- referrer,
- ));
- }
- CjsAnalysis::Cjs(analysis) => {
- if !analysis.reexports.is_empty() {
- handle_reexports(
- reexport_specifier.clone(),
- analysis.reexports,
- &mut analyze_futures,
- errors,
- );
- }
-
- all_exports.extend(
- analysis
- .exports
- .into_iter()
- .filter(|e| e.as_str() != "default"),
- );
- }
- }
- }
- }
-
- // todo(dsherret): what is going on here? Isn't this a bunch of duplicate code?
- fn resolve(
- &self,
- specifier: &str,
- referrer: &ModuleSpecifier,
- conditions: &[&str],
- mode: NodeResolutionMode,
- ) -> Result<ModuleSpecifier, AnyError> {
- if specifier.starts_with('/') {
- todo!();
- }
-
- let referrer_path = referrer.to_file_path().unwrap();
- if specifier.starts_with("./") || specifier.starts_with("../") {
- if let Some(parent) = referrer_path.parent() {
- return self
- .file_extension_probe(parent.join(specifier), &referrer_path)
- .map(|p| to_file_specifier(&p));
- } else {
- todo!();
- }
- }
-
- // We've got a bare specifier or maybe bare_specifier/blah.js"
- let (package_specifier, package_subpath) =
- parse_specifier(specifier).unwrap();
-
- // todo(dsherret): use not_found error on not found here
- let module_dir = self.npm_resolver.resolve_package_folder_from_package(
- package_specifier.as_str(),
- referrer,
- )?;
-
- let package_json_path = module_dir.join("package.json");
- let maybe_package_json = load_pkg_json(&*self.fs, &package_json_path)?;
- if let Some(package_json) = maybe_package_json {
- if let Some(exports) = &package_json.exports {
- return self
- .node_resolver
- .package_exports_resolve(
- &package_json_path,
- &package_subpath,
- exports,
- Some(referrer),
- NodeModuleKind::Esm,
- conditions,
- mode,
- )
- .map_err(AnyError::from);
- }
-
- // old school
- if package_subpath != "." {
- let d = module_dir.join(package_subpath);
- if self.fs.is_dir_sync(&d) {
- // subdir might have a package.json that specifies the entrypoint
- let package_json_path = d.join("package.json");
- let maybe_package_json =
- load_pkg_json(&*self.fs, &package_json_path)?;
- if let Some(package_json) = maybe_package_json {
- if let Some(main) = package_json.main(NodeModuleKind::Cjs) {
- return Ok(to_file_specifier(&d.join(main).clean()));
- }
- }
-
- return Ok(to_file_specifier(&d.join("index.js").clean()));
- }
- return self
- .file_extension_probe(d, &referrer_path)
- .map(|p| to_file_specifier(&p));
- } else if let Some(main) = package_json.main(NodeModuleKind::Cjs) {
- return Ok(to_file_specifier(&module_dir.join(main).clean()));
- } else {
- return Ok(to_file_specifier(&module_dir.join("index.js").clean()));
- }
- }
-
- // as a fallback, attempt to resolve it via the ancestor directories
- let mut last = referrer_path.as_path();
- while let Some(parent) = last.parent() {
- if !self.npm_resolver.in_npm_package_at_dir_path(parent) {
- break;
- }
- let path = if parent.ends_with("node_modules") {
- parent.join(specifier)
- } else {
- parent.join("node_modules").join(specifier)
- };
- if let Ok(path) = self.file_extension_probe(path, &referrer_path) {
- return Ok(to_file_specifier(&path));
- }
- last = parent;
- }
-
- Err(not_found(specifier, &referrer_path))
- }
-
- fn file_extension_probe(
- &self,
- p: PathBuf,
- referrer: &Path,
- ) -> Result<PathBuf, AnyError> {
- let p = p.clean();
- if self.fs.exists_sync(&p) {
- let file_name = p.file_name().unwrap();
- let p_js =
- p.with_file_name(format!("{}.js", file_name.to_str().unwrap()));
- if self.fs.is_file_sync(&p_js) {
- return Ok(p_js);
- } else if self.fs.is_dir_sync(&p) {
- return Ok(p.join("index.js"));
- } else {
- return Ok(p);
- }
- } else if let Some(file_name) = p.file_name() {
- {
- let p_js =
- p.with_file_name(format!("{}.js", file_name.to_str().unwrap()));
- if self.fs.is_file_sync(&p_js) {
- return Ok(p_js);
- }
- }
- {
- let p_json =
- p.with_file_name(format!("{}.json", file_name.to_str().unwrap()));
- if self.fs.is_file_sync(&p_json) {
- return Ok(p_json);
- }
- }
- }
- Err(not_found(&p.to_string_lossy(), referrer))
- }
-}
-
-static RESERVED_WORDS: Lazy<HashSet<&str>> = Lazy::new(|| {
- HashSet::from([
- "abstract",
- "arguments",
- "async",
- "await",
- "boolean",
- "break",
- "byte",
- "case",
- "catch",
- "char",
- "class",
- "const",
- "continue",
- "debugger",
- "default",
- "delete",
- "do",
- "double",
- "else",
- "enum",
- "eval",
- "export",
- "extends",
- "false",
- "final",
- "finally",
- "float",
- "for",
- "function",
- "get",
- "goto",
- "if",
- "implements",
- "import",
- "in",
- "instanceof",
- "int",
- "interface",
- "let",
- "long",
- "mod",
- "native",
- "new",
- "null",
- "package",
- "private",
- "protected",
- "public",
- "return",
- "set",
- "short",
- "static",
- "super",
- "switch",
- "synchronized",
- "this",
- "throw",
- "throws",
- "transient",
- "true",
- "try",
- "typeof",
- "var",
- "void",
- "volatile",
- "while",
- "with",
- "yield",
- ])
-});
-
-fn add_export(
- source: &mut Vec<String>,
- name: &str,
- initializer: &str,
- temp_var_count: &mut usize,
-) {
- fn is_valid_var_decl(name: &str) -> bool {
- // it's ok to be super strict here
- if name.is_empty() {
- return false;
- }
-
- if let Some(first) = name.chars().next() {
- if !first.is_ascii_alphabetic() && first != '_' && first != '$' {
- return false;
- }
- }
-
- name
- .chars()
- .all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '$')
- }
-
- // TODO(bartlomieju): Node actually checks if a given export exists in `exports` object,
- // but it might not be necessary here since our analysis is more detailed?
- if RESERVED_WORDS.contains(name) || !is_valid_var_decl(name) {
- *temp_var_count += 1;
- // we can't create an identifier with a reserved word or invalid identifier name,
- // so assign it to a temporary variable that won't have a conflict, then re-export
- // it as a string
- source.push(format!(
- "const __deno_export_{temp_var_count}__ = {initializer};"
- ));
- source.push(format!(
- "export {{ __deno_export_{temp_var_count}__ as \"{}\" }};",
- escape_for_double_quote_string(name)
- ));
- } else {
- source.push(format!("export const {name} = {initializer};"));
- }
-}
-
-fn parse_specifier(specifier: &str) -> Option<(String, String)> {
- let mut separator_index = specifier.find('/');
- let mut valid_package_name = true;
- // let mut is_scoped = false;
- if specifier.is_empty() {
- valid_package_name = false;
- } else if specifier.starts_with('@') {
- // is_scoped = true;
- if let Some(index) = separator_index {
- separator_index = specifier[index + 1..].find('/').map(|i| i + index + 1);
- } else {
- valid_package_name = false;
- }
- }
-
- let package_name = if let Some(index) = separator_index {
- specifier[0..index].to_string()
- } else {
- specifier.to_string()
- };
-
- // Package name cannot have leading . and cannot have percent-encoding or separators.
- for ch in package_name.chars() {
- if ch == '%' || ch == '\\' {
- valid_package_name = false;
- break;
- }
- }
-
- if !valid_package_name {
- return None;
- }
-
- let package_subpath = if let Some(index) = separator_index {
- format!(".{}", specifier.chars().skip(index).collect::<String>())
- } else {
- ".".to_string()
- };
-
- Some((package_name, package_subpath))
-}
-
-fn not_found(path: &str, referrer: &Path) -> AnyError {
- let msg = format!(
- "[ERR_MODULE_NOT_FOUND] Cannot find module \"{}\" imported from \"{}\"",
- path,
- referrer.to_string_lossy()
- );
- std::io::Error::new(std::io::ErrorKind::NotFound, msg).into()
-}
-
-fn escape_for_double_quote_string(text: &str) -> String {
- text.replace('\\', "\\\\").replace('"', "\\\"")
-}
-#[cfg(test)]
-mod tests {
- use super::*;
-
- #[test]
- fn test_add_export() {
- let mut temp_var_count = 0;
- let mut source = vec![];
-
- let exports = vec!["static", "server", "app", "dashed-export", "3d"];
- for export in exports {
- add_export(&mut source, export, "init", &mut temp_var_count);
- }
- assert_eq!(
- source,
- vec![
- "const __deno_export_1__ = init;".to_string(),
- "export { __deno_export_1__ as \"static\" };".to_string(),
- "export const server = init;".to_string(),
- "export const app = init;".to_string(),
- "const __deno_export_2__ = init;".to_string(),
- "export { __deno_export_2__ as \"dashed-export\" };".to_string(),
- "const __deno_export_3__ = init;".to_string(),
- "export { __deno_export_3__ as \"3d\" };".to_string(),
- ]
- )
- }
-
- #[test]
- fn test_parse_specifier() {
- assert_eq!(
- parse_specifier("@some-package/core/actions"),
- Some(("@some-package/core".to_string(), "./actions".to_string()))
- );
- }
-}
diff --git a/ext/node/errors.rs b/ext/node/errors.rs
deleted file mode 100644
index 64625d32f..000000000
--- a/ext/node/errors.rs
+++ /dev/null
@@ -1,769 +0,0 @@
-// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
-
-use std::borrow::Cow;
-use std::fmt::Write;
-use std::path::PathBuf;
-
-use deno_core::ModuleSpecifier;
-use thiserror::Error;
-
-use crate::NodeModuleKind;
-use crate::NodeResolutionMode;
-
-macro_rules! kinded_err {
- ($name:ident, $kind_name:ident) => {
- #[derive(Error, Debug)]
- #[error(transparent)]
- pub struct $name(pub Box<$kind_name>);
-
- impl $name {
- pub fn as_kind(&self) -> &$kind_name {
- &self.0
- }
-
- pub fn into_kind(self) -> $kind_name {
- *self.0
- }
- }
-
- impl<E> From<E> for $name
- where
- $kind_name: From<E>,
- {
- fn from(err: E) -> Self {
- $name(Box::new($kind_name::from(err)))
- }
- }
- };
-}
-
-#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
-#[allow(non_camel_case_types)]
-pub enum NodeJsErrorCode {
- ERR_INVALID_MODULE_SPECIFIER,
- ERR_INVALID_PACKAGE_CONFIG,
- ERR_INVALID_PACKAGE_TARGET,
- ERR_MODULE_NOT_FOUND,
- ERR_PACKAGE_IMPORT_NOT_DEFINED,
- ERR_PACKAGE_PATH_NOT_EXPORTED,
- ERR_UNKNOWN_FILE_EXTENSION,
- ERR_UNSUPPORTED_DIR_IMPORT,
- ERR_UNSUPPORTED_ESM_URL_SCHEME,
- /// Deno specific since Node doesn't support TypeScript.
- ERR_TYPES_NOT_FOUND,
-}
-
-impl std::fmt::Display for NodeJsErrorCode {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- write!(f, "{}", self.as_str())
- }
-}
-
-impl NodeJsErrorCode {
- pub fn as_str(&self) -> &'static str {
- use NodeJsErrorCode::*;
- match self {
- ERR_INVALID_MODULE_SPECIFIER => "ERR_INVALID_MODULE_SPECIFIER",
- ERR_INVALID_PACKAGE_CONFIG => "ERR_INVALID_PACKAGE_CONFIG",
- ERR_INVALID_PACKAGE_TARGET => "ERR_INVALID_PACKAGE_TARGET",
- ERR_MODULE_NOT_FOUND => "ERR_MODULE_NOT_FOUND",
- ERR_PACKAGE_IMPORT_NOT_DEFINED => "ERR_PACKAGE_IMPORT_NOT_DEFINED",
- ERR_PACKAGE_PATH_NOT_EXPORTED => "ERR_PACKAGE_PATH_NOT_EXPORTED",
- ERR_UNKNOWN_FILE_EXTENSION => "ERR_UNKNOWN_FILE_EXTENSION",
- ERR_UNSUPPORTED_DIR_IMPORT => "ERR_UNSUPPORTED_DIR_IMPORT",
- ERR_UNSUPPORTED_ESM_URL_SCHEME => "ERR_UNSUPPORTED_ESM_URL_SCHEME",
- ERR_TYPES_NOT_FOUND => "ERR_TYPES_NOT_FOUND",
- }
- }
-}
-
-pub trait NodeJsErrorCoded {
- fn code(&self) -> NodeJsErrorCode;
-}
-
-kinded_err!(
- ResolvePkgSubpathFromDenoModuleError,
- ResolvePkgSubpathFromDenoModuleErrorKind
-);
-
-impl NodeJsErrorCoded for ResolvePkgSubpathFromDenoModuleError {
- fn code(&self) -> NodeJsErrorCode {
- use ResolvePkgSubpathFromDenoModuleErrorKind::*;
- match self.as_kind() {
- PackageSubpathResolve(e) => e.code(),
- UrlToNodeResolution(e) => e.code(),
- }
- }
-}
-
-#[derive(Debug, Error)]
-pub enum ResolvePkgSubpathFromDenoModuleErrorKind {
- #[error(transparent)]
- PackageSubpathResolve(#[from] PackageSubpathResolveError),
- #[error(transparent)]
- UrlToNodeResolution(#[from] UrlToNodeResolutionError),
-}
-
-// todo(https://github.com/denoland/deno_core/issues/810): make this a TypeError
-#[derive(Debug, Clone, Error)]
-#[error(
- "[{}] Invalid module '{}' {}{}",
- self.code(),
- request,
- reason,
- maybe_referrer.as_ref().map(|referrer| format!(" imported from '{}'", referrer)).unwrap_or_default()
-)]
-pub struct InvalidModuleSpecifierError {
- pub request: String,
- pub reason: Cow<'static, str>,
- pub maybe_referrer: Option<String>,
-}
-
-impl NodeJsErrorCoded for InvalidModuleSpecifierError {
- fn code(&self) -> NodeJsErrorCode {
- NodeJsErrorCode::ERR_INVALID_MODULE_SPECIFIER
- }
-}
-
-kinded_err!(LegacyResolveError, LegacyResolveErrorKind);
-
-#[derive(Debug, Error)]
-pub enum LegacyResolveErrorKind {
- #[error(transparent)]
- TypesNotFound(#[from] TypesNotFoundError),
- #[error(transparent)]
- ModuleNotFound(#[from] ModuleNotFoundError),
-}
-
-impl NodeJsErrorCoded for LegacyResolveError {
- fn code(&self) -> NodeJsErrorCode {
- match self.as_kind() {
- LegacyResolveErrorKind::TypesNotFound(e) => e.code(),
- LegacyResolveErrorKind::ModuleNotFound(e) => e.code(),
- }
- }
-}
-
-kinded_err!(PackageFolderResolveError, PackageFolderResolveErrorKind);
-
-#[derive(Debug, Error)]
-#[error(
- "Could not find package '{}' from referrer '{}'{}.",
- package_name,
- referrer,
- referrer_extra.as_ref().map(|r| format!(" ({})", r)).unwrap_or_default()
-)]
-pub struct PackageNotFoundError {
- pub package_name: String,
- pub referrer: ModuleSpecifier,
- /// Extra information about the referrer.
- pub referrer_extra: Option<String>,
-}
-
-impl NodeJsErrorCoded for PackageNotFoundError {
- fn code(&self) -> NodeJsErrorCode {
- NodeJsErrorCode::ERR_MODULE_NOT_FOUND
- }
-}
-
-#[derive(Debug, Error)]
-#[error(
- "Could not find referrer npm package '{}'{}.",
- referrer,
- referrer_extra.as_ref().map(|r| format!(" ({})", r)).unwrap_or_default()
-)]
-pub struct ReferrerNotFoundError {
- pub referrer: ModuleSpecifier,
- /// Extra information about the referrer.
- pub referrer_extra: Option<String>,
-}
-
-impl NodeJsErrorCoded for ReferrerNotFoundError {
- fn code(&self) -> NodeJsErrorCode {
- NodeJsErrorCode::ERR_MODULE_NOT_FOUND
- }
-}
-
-#[derive(Debug, Error)]
-#[error("Failed resolving '{package_name}' from referrer '{referrer}'.")]
-pub struct PackageFolderResolveIoError {
- pub package_name: String,
- pub referrer: ModuleSpecifier,
- #[source]
- pub source: std::io::Error,
-}
-
-impl NodeJsErrorCoded for PackageFolderResolveIoError {
- fn code(&self) -> NodeJsErrorCode {
- NodeJsErrorCode::ERR_MODULE_NOT_FOUND
- }
-}
-
-impl NodeJsErrorCoded for PackageFolderResolveError {
- fn code(&self) -> NodeJsErrorCode {
- match self.as_kind() {
- PackageFolderResolveErrorKind::PackageNotFound(e) => e.code(),
- PackageFolderResolveErrorKind::ReferrerNotFound(e) => e.code(),
- PackageFolderResolveErrorKind::Io(e) => e.code(),
- }
- }
-}
-
-#[derive(Debug, Error)]
-pub enum PackageFolderResolveErrorKind {
- #[error(transparent)]
- PackageNotFound(#[from] PackageNotFoundError),
- #[error(transparent)]
- ReferrerNotFound(#[from] ReferrerNotFoundError),
- #[error(transparent)]
- Io(#[from] PackageFolderResolveIoError),
-}
-
-kinded_err!(PackageSubpathResolveError, PackageSubpathResolveErrorKind);
-
-impl NodeJsErrorCoded for PackageSubpathResolveError {
- fn code(&self) -> NodeJsErrorCode {
- match self.as_kind() {
- PackageSubpathResolveErrorKind::PkgJsonLoad(e) => e.code(),
- PackageSubpathResolveErrorKind::Exports(e) => e.code(),
- PackageSubpathResolveErrorKind::LegacyResolve(e) => e.code(),
- }
- }
-}
-
-#[derive(Debug, Error)]
-pub enum PackageSubpathResolveErrorKind {
- #[error(transparent)]
- PkgJsonLoad(#[from] PackageJsonLoadError),
- #[error(transparent)]
- Exports(PackageExportsResolveError),
- #[error(transparent)]
- LegacyResolve(LegacyResolveError),
-}
-
-#[derive(Debug, Error)]
-#[error(
- "Target '{}' not found from '{}'{}{}.",
- target,
- pkg_json_path.display(),
- maybe_referrer.as_ref().map(|r|
- format!(
- " from{} referrer {}",
- match referrer_kind {
- NodeModuleKind::Esm => "",
- NodeModuleKind::Cjs => " cjs",
- },
- r
- )
- ).unwrap_or_default(),
- match mode {
- NodeResolutionMode::Execution => "",
- NodeResolutionMode::Types => " for types",
- }
-)]
-pub struct PackageTargetNotFoundError {
- pub pkg_json_path: PathBuf,
- pub target: String,
- pub maybe_referrer: Option<ModuleSpecifier>,
- pub referrer_kind: NodeModuleKind,
- pub mode: NodeResolutionMode,
-}
-
-impl NodeJsErrorCoded for PackageTargetNotFoundError {
- fn code(&self) -> NodeJsErrorCode {
- NodeJsErrorCode::ERR_MODULE_NOT_FOUND
- }
-}
-
-kinded_err!(PackageTargetResolveError, PackageTargetResolveErrorKind);
-
-impl NodeJsErrorCoded for PackageTargetResolveError {
- fn code(&self) -> NodeJsErrorCode {
- match self.as_kind() {
- PackageTargetResolveErrorKind::NotFound(e) => e.code(),
- PackageTargetResolveErrorKind::InvalidPackageTarget(e) => e.code(),
- PackageTargetResolveErrorKind::InvalidModuleSpecifier(e) => e.code(),
- PackageTargetResolveErrorKind::PackageResolve(e) => e.code(),
- PackageTargetResolveErrorKind::TypesNotFound(e) => e.code(),
- }
- }
-}
-
-#[derive(Debug, Error)]
-pub enum PackageTargetResolveErrorKind {
- #[error(transparent)]
- NotFound(#[from] PackageTargetNotFoundError),
- #[error(transparent)]
- InvalidPackageTarget(#[from] InvalidPackageTargetError),
- #[error(transparent)]
- InvalidModuleSpecifier(#[from] InvalidModuleSpecifierError),
- #[error(transparent)]
- PackageResolve(#[from] PackageResolveError),
- #[error(transparent)]
- TypesNotFound(#[from] TypesNotFoundError),
-}
-
-kinded_err!(PackageExportsResolveError, PackageExportsResolveErrorKind);
-
-impl NodeJsErrorCoded for PackageExportsResolveError {
- fn code(&self) -> NodeJsErrorCode {
- match self.as_kind() {
- PackageExportsResolveErrorKind::PackagePathNotExported(e) => e.code(),
- PackageExportsResolveErrorKind::PackageTargetResolve(e) => e.code(),
- }
- }
-}
-
-#[derive(Debug, Error)]
-pub enum PackageExportsResolveErrorKind {
- #[error(transparent)]
- PackagePathNotExported(#[from] PackagePathNotExportedError),
- #[error(transparent)]
- PackageTargetResolve(#[from] PackageTargetResolveError),
-}
-
-#[derive(Debug, Error)]
-#[error(
- "[{}] Could not find types for '{}'{}",
- self.code(),
- self.0.code_specifier,
- self.0.maybe_referrer.as_ref().map(|r| format!(" imported from '{}'", r)).unwrap_or_default(),
- )]
-pub struct TypesNotFoundError(pub Box<TypesNotFoundErrorData>);
-
-#[derive(Debug)]
-pub struct TypesNotFoundErrorData {
- pub code_specifier: ModuleSpecifier,
- pub maybe_referrer: Option<ModuleSpecifier>,
-}
-
-impl NodeJsErrorCoded for TypesNotFoundError {
- fn code(&self) -> NodeJsErrorCode {
- NodeJsErrorCode::ERR_TYPES_NOT_FOUND
- }
-}
-
-#[derive(Debug, Error)]
-#[error(
- "[{}] Invalid package config. {}",
- self.code(),
- self.0
-)]
-pub struct PackageJsonLoadError(
- #[source]
- #[from]
- pub deno_package_json::PackageJsonLoadError,
-);
-
-impl NodeJsErrorCoded for PackageJsonLoadError {
- fn code(&self) -> NodeJsErrorCode {
- NodeJsErrorCode::ERR_INVALID_PACKAGE_CONFIG
- }
-}
-
-kinded_err!(ClosestPkgJsonError, ClosestPkgJsonErrorKind);
-
-impl NodeJsErrorCoded for ClosestPkgJsonError {
- fn code(&self) -> NodeJsErrorCode {
- match self.as_kind() {
- ClosestPkgJsonErrorKind::CanonicalizingDir(e) => e.code(),
- ClosestPkgJsonErrorKind::Load(e) => e.code(),
- }
- }
-}
-
-#[derive(Debug, Error)]
-pub enum ClosestPkgJsonErrorKind {
- #[error(transparent)]
- CanonicalizingDir(#[from] CanonicalizingPkgJsonDirError),
- #[error(transparent)]
- Load(#[from] PackageJsonLoadError),
-}
-
-#[derive(Debug, Error)]
-#[error("[{}] Failed canonicalizing package.json directory '{}'.", self.code(), dir_path.display())]
-pub struct CanonicalizingPkgJsonDirError {
- pub dir_path: PathBuf,
- #[source]
- pub source: std::io::Error,
-}
-
-impl NodeJsErrorCoded for CanonicalizingPkgJsonDirError {
- fn code(&self) -> NodeJsErrorCode {
- NodeJsErrorCode::ERR_MODULE_NOT_FOUND
- }
-}
-
-#[derive(Debug, Error)]
-#[error("TypeScript files are not supported in npm packages: {specifier}")]
-pub struct TypeScriptNotSupportedInNpmError {
- pub specifier: ModuleSpecifier,
-}
-
-impl NodeJsErrorCoded for TypeScriptNotSupportedInNpmError {
- fn code(&self) -> NodeJsErrorCode {
- NodeJsErrorCode::ERR_UNKNOWN_FILE_EXTENSION
- }
-}
-
-kinded_err!(UrlToNodeResolutionError, UrlToNodeResolutionErrorKind);
-
-impl NodeJsErrorCoded for UrlToNodeResolutionError {
- fn code(&self) -> NodeJsErrorCode {
- match self.as_kind() {
- UrlToNodeResolutionErrorKind::TypeScriptNotSupported(e) => e.code(),
- UrlToNodeResolutionErrorKind::ClosestPkgJson(e) => e.code(),
- }
- }
-}
-
-#[derive(Debug, Error)]
-pub enum UrlToNodeResolutionErrorKind {
- #[error(transparent)]
- TypeScriptNotSupported(#[from] TypeScriptNotSupportedInNpmError),
- #[error(transparent)]
- ClosestPkgJson(#[from] ClosestPkgJsonError),
-}
-
-// todo(https://github.com/denoland/deno_core/issues/810): make this a TypeError
-#[derive(Debug, Error)]
-#[error(
- "[{}] Package import specifier \"{}\" is not defined{}{}",
- self.code(),
- name,
- package_json_path.as_ref().map(|p| format!(" in package {}", p.display())).unwrap_or_default(),
- maybe_referrer.as_ref().map(|r| format!(" imported from '{}'", r)).unwrap_or_default(),
-)]
-pub struct PackageImportNotDefinedError {
- pub name: String,
- pub package_json_path: Option<PathBuf>,
- pub maybe_referrer: Option<ModuleSpecifier>,
-}
-
-impl NodeJsErrorCoded for PackageImportNotDefinedError {
- fn code(&self) -> NodeJsErrorCode {
- NodeJsErrorCode::ERR_PACKAGE_IMPORT_NOT_DEFINED
- }
-}
-
-kinded_err!(PackageImportsResolveError, PackageImportsResolveErrorKind);
-
-#[derive(Debug, Error)]
-pub enum PackageImportsResolveErrorKind {
- #[error(transparent)]
- ClosestPkgJson(ClosestPkgJsonError),
- #[error(transparent)]
- InvalidModuleSpecifier(#[from] InvalidModuleSpecifierError),
- #[error(transparent)]
- NotDefined(#[from] PackageImportNotDefinedError),
- #[error(transparent)]
- Target(#[from] PackageTargetResolveError),
-}
-
-impl NodeJsErrorCoded for PackageImportsResolveErrorKind {
- fn code(&self) -> NodeJsErrorCode {
- match self {
- Self::ClosestPkgJson(e) => e.code(),
- Self::InvalidModuleSpecifier(e) => e.code(),
- Self::NotDefined(e) => e.code(),
- Self::Target(e) => e.code(),
- }
- }
-}
-
-kinded_err!(PackageResolveError, PackageResolveErrorKind);
-
-impl NodeJsErrorCoded for PackageResolveError {
- fn code(&self) -> NodeJsErrorCode {
- match self.as_kind() {
- PackageResolveErrorKind::ClosestPkgJson(e) => e.code(),
- PackageResolveErrorKind::InvalidModuleSpecifier(e) => e.code(),
- PackageResolveErrorKind::PackageFolderResolve(e) => e.code(),
- PackageResolveErrorKind::ExportsResolve(e) => e.code(),
- PackageResolveErrorKind::SubpathResolve(e) => e.code(),
- }
- }
-}
-
-#[derive(Debug, Error)]
-pub enum PackageResolveErrorKind {
- #[error(transparent)]
- ClosestPkgJson(#[from] ClosestPkgJsonError),
- #[error(transparent)]
- InvalidModuleSpecifier(#[from] InvalidModuleSpecifierError),
- #[error(transparent)]
- PackageFolderResolve(#[from] PackageFolderResolveError),
- #[error(transparent)]
- ExportsResolve(#[from] PackageExportsResolveError),
- #[error(transparent)]
- SubpathResolve(#[from] PackageSubpathResolveError),
-}
-
-#[derive(Debug, Error)]
-#[error("Failed joining '{path}' from '{base}'.")]
-pub struct NodeResolveRelativeJoinError {
- pub path: String,
- pub base: ModuleSpecifier,
- #[source]
- pub source: url::ParseError,
-}
-
-#[derive(Debug, Error)]
-#[error("Failed resolving specifier from data url referrer.")]
-pub struct DataUrlReferrerError {
- #[source]
- pub source: url::ParseError,
-}
-
-kinded_err!(NodeResolveError, NodeResolveErrorKind);
-
-#[derive(Debug, Error)]
-pub enum NodeResolveErrorKind {
- #[error(transparent)]
- RelativeJoin(#[from] NodeResolveRelativeJoinError),
- #[error(transparent)]
- PackageImportsResolve(#[from] PackageImportsResolveError),
- #[error(transparent)]
- UnsupportedEsmUrlScheme(#[from] UnsupportedEsmUrlSchemeError),
- #[error(transparent)]
- DataUrlReferrer(#[from] DataUrlReferrerError),
- #[error(transparent)]
- PackageResolve(#[from] PackageResolveError),
- #[error(transparent)]
- TypesNotFound(#[from] TypesNotFoundError),
- #[error(transparent)]
- FinalizeResolution(#[from] FinalizeResolutionError),
- #[error(transparent)]
- UrlToNodeResolution(#[from] UrlToNodeResolutionError),
-}
-
-kinded_err!(FinalizeResolutionError, FinalizeResolutionErrorKind);
-
-#[derive(Debug, Error)]
-pub enum FinalizeResolutionErrorKind {
- #[error(transparent)]
- InvalidModuleSpecifierError(#[from] InvalidModuleSpecifierError),
- #[error(transparent)]
- ModuleNotFound(#[from] ModuleNotFoundError),
- #[error(transparent)]
- UnsupportedDirImport(#[from] UnsupportedDirImportError),
-}
-
-impl NodeJsErrorCoded for FinalizeResolutionError {
- fn code(&self) -> NodeJsErrorCode {
- match self.as_kind() {
- FinalizeResolutionErrorKind::InvalidModuleSpecifierError(e) => e.code(),
- FinalizeResolutionErrorKind::ModuleNotFound(e) => e.code(),
- FinalizeResolutionErrorKind::UnsupportedDirImport(e) => e.code(),
- }
- }
-}
-
-#[derive(Debug, Error)]
-#[error(
- "[{}] Cannot find {} '{}'{}",
- self.code(),
- typ,
- specifier,
- maybe_referrer.as_ref().map(|referrer| format!(" imported from '{}'", referrer)).unwrap_or_default()
-)]
-pub struct ModuleNotFoundError {
- pub specifier: ModuleSpecifier,
- pub maybe_referrer: Option<ModuleSpecifier>,
- pub typ: &'static str,
-}
-
-impl NodeJsErrorCoded for ModuleNotFoundError {
- fn code(&self) -> NodeJsErrorCode {
- NodeJsErrorCode::ERR_MODULE_NOT_FOUND
- }
-}
-
-#[derive(Debug, Error)]
-#[error(
- "[{}] Directory import '{}' is not supported resolving ES modules{}",
- self.code(),
- dir_url,
- maybe_referrer.as_ref().map(|referrer| format!(" imported from '{}'", referrer)).unwrap_or_default(),
-)]
-pub struct UnsupportedDirImportError {
- pub dir_url: ModuleSpecifier,
- pub maybe_referrer: Option<ModuleSpecifier>,
-}
-
-impl NodeJsErrorCoded for UnsupportedDirImportError {
- fn code(&self) -> NodeJsErrorCode {
- NodeJsErrorCode::ERR_UNSUPPORTED_DIR_IMPORT
- }
-}
-
-#[derive(Debug)]
-pub struct InvalidPackageTargetError {
- pub pkg_json_path: PathBuf,
- pub sub_path: String,
- pub target: String,
- pub is_import: bool,
- pub maybe_referrer: Option<ModuleSpecifier>,
-}
-
-impl std::error::Error for InvalidPackageTargetError {}
-
-impl std::fmt::Display for InvalidPackageTargetError {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- let rel_error = !self.is_import
- && !self.target.is_empty()
- && !self.target.starts_with("./");
- f.write_char('[')?;
- f.write_str(self.code().as_str())?;
- f.write_char(']')?;
-
- if self.sub_path == "." {
- assert!(!self.is_import);
- write!(
- f,
- " Invalid \"exports\" main target {} defined in the package config {}",
- self.target,
- self.pkg_json_path.display()
- )?;
- } else {
- let ie = if self.is_import { "imports" } else { "exports" };
- write!(
- f,
- " Invalid \"{}\" target {} defined for '{}' in the package config {}",
- ie,
- self.target,
- self.sub_path,
- self.pkg_json_path.display()
- )?;
- };
-
- if let Some(referrer) = &self.maybe_referrer {
- write!(f, " imported from '{}'", referrer)?;
- }
- if rel_error {
- write!(f, "; target must start with \"./\"")?;
- }
- Ok(())
- }
-}
-
-impl NodeJsErrorCoded for InvalidPackageTargetError {
- fn code(&self) -> NodeJsErrorCode {
- NodeJsErrorCode::ERR_INVALID_PACKAGE_TARGET
- }
-}
-
-#[derive(Debug)]
-pub struct PackagePathNotExportedError {
- pub pkg_json_path: PathBuf,
- pub subpath: String,
- pub maybe_referrer: Option<ModuleSpecifier>,
- pub mode: NodeResolutionMode,
-}
-
-impl NodeJsErrorCoded for PackagePathNotExportedError {
- fn code(&self) -> NodeJsErrorCode {
- NodeJsErrorCode::ERR_PACKAGE_PATH_NOT_EXPORTED
- }
-}
-
-impl std::error::Error for PackagePathNotExportedError {}
-
-impl std::fmt::Display for PackagePathNotExportedError {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- f.write_char('[')?;
- f.write_str(self.code().as_str())?;
- f.write_char(']')?;
-
- let types_msg = match self.mode {
- NodeResolutionMode::Execution => String::new(),
- NodeResolutionMode::Types => " for types".to_string(),
- };
- if self.subpath == "." {
- write!(
- f,
- " No \"exports\" main defined{} in '{}'",
- types_msg,
- self.pkg_json_path.display()
- )?;
- } else {
- write!(
- f,
- " Package subpath '{}' is not defined{} by \"exports\" in '{}'",
- self.subpath,
- types_msg,
- self.pkg_json_path.display()
- )?;
- };
-
- if let Some(referrer) = &self.maybe_referrer {
- write!(f, " imported from '{}'", referrer)?;
- }
- Ok(())
- }
-}
-
-#[derive(Debug, Clone, Error)]
-#[error(
- "[{}] Only file and data URLs are supported by the default ESM loader.{} Received protocol '{}'",
- self.code(),
- if cfg!(windows) && url_scheme.len() == 2 { " On Windows, absolute path must be valid file:// URLS."} else { "" },
- url_scheme
-)]
-pub struct UnsupportedEsmUrlSchemeError {
- pub url_scheme: String,
-}
-
-impl NodeJsErrorCoded for UnsupportedEsmUrlSchemeError {
- fn code(&self) -> NodeJsErrorCode {
- NodeJsErrorCode::ERR_UNSUPPORTED_ESM_URL_SCHEME
- }
-}
-
-#[derive(Debug, Error)]
-pub enum ResolvePkgJsonBinExportError {
- #[error(transparent)]
- PkgJsonLoad(#[from] PackageJsonLoadError),
- #[error("Failed resolving binary export. '{}' did not exist", pkg_json_path.display())]
- MissingPkgJson { pkg_json_path: PathBuf },
- #[error("Failed resolving binary export. {message}")]
- InvalidBinProperty { message: String },
- #[error(transparent)]
- UrlToNodeResolution(#[from] UrlToNodeResolutionError),
-}
-
-#[derive(Debug, Error)]
-pub enum ResolveBinaryCommandsError {
- #[error(transparent)]
- PkgJsonLoad(#[from] PackageJsonLoadError),
- #[error("'{}' did not have a name", pkg_json_path.display())]
- MissingPkgJsonName { pkg_json_path: PathBuf },
-}
-
-#[cfg(test)]
-mod test {
- use super::*;
-
- #[test]
- fn types_resolution_package_path_not_exported() {
- let separator_char = if cfg!(windows) { '\\' } else { '/' };
- assert_eq!(
- PackagePathNotExportedError {
- pkg_json_path: PathBuf::from("test_path").join("package.json"),
- subpath: "./jsx-runtime".to_string(),
- maybe_referrer: None,
- mode: NodeResolutionMode::Types
- }.to_string(),
- format!("[ERR_PACKAGE_PATH_NOT_EXPORTED] Package subpath './jsx-runtime' is not defined for types by \"exports\" in 'test_path{separator_char}package.json'")
- );
- assert_eq!(
- PackagePathNotExportedError {
- pkg_json_path: PathBuf::from("test_path").join("package.json"),
- subpath: ".".to_string(),
- maybe_referrer: None,
- mode: NodeResolutionMode::Types
- }.to_string(),
- format!("[ERR_PACKAGE_PATH_NOT_EXPORTED] No \"exports\" main defined for types in 'test_path{separator_char}package.json'")
- );
- }
-}
diff --git a/ext/node/global.rs b/ext/node/global.rs
index 7f901fd03..618e68494 100644
--- a/ext/node/global.rs
+++ b/ext/node/global.rs
@@ -6,7 +6,7 @@ use deno_core::v8;
use deno_core::v8::GetPropertyNamesArgs;
use deno_core::v8::MapFnTo;
-use crate::resolution::NodeResolverRc;
+use crate::NodeResolverRc;
// NOTE(bartlomieju): somehow calling `.map_fn_to()` multiple times on a function
// returns two different pointers. That shouldn't be the case as `.map_fn_to()`
diff --git a/ext/node/lib.rs b/ext/node/lib.rs
index 21af5a094..2c8650577 100644
--- a/ext/node/lib.rs
+++ b/ext/node/lib.rs
@@ -5,7 +5,6 @@
use std::collections::HashSet;
use std::path::Path;
-use std::path::PathBuf;
use deno_core::error::AnyError;
use deno_core::located_script_name;
@@ -15,24 +14,20 @@ use deno_core::url::Url;
use deno_core::v8;
use deno_core::v8::ExternalReference;
use deno_core::JsRuntime;
-use deno_core::ModuleSpecifier;
use deno_core::OpState;
use deno_fs::sync::MaybeSend;
use deno_fs::sync::MaybeSync;
+use node_resolver::NpmResolverRc;
use once_cell::sync::Lazy;
extern crate libz_sys as zlib;
-pub mod analyze;
-pub mod errors;
mod global;
mod ops;
-mod package_json;
-mod path;
mod polyfill;
-mod resolution;
pub use deno_package_json::PackageJson;
+pub use node_resolver::PathClean;
pub use ops::ipc::ChildPipeFd;
pub use ops::ipc::IpcJsonStreamResource;
use ops::vm;
@@ -40,17 +35,9 @@ pub use ops::vm::create_v8_context;
pub use ops::vm::init_global_template;
pub use ops::vm::ContextInitMode;
pub use ops::vm::VM_CONTEXT_INDEX;
-pub use package_json::load_pkg_json;
-pub use package_json::PackageJsonThreadLocalCache;
-pub use path::PathClean;
pub use polyfill::is_builtin_node_module;
pub use polyfill::SUPPORTED_BUILTIN_NODE_MODULES;
pub use polyfill::SUPPORTED_BUILTIN_NODE_MODULES_WITH_PREFIX;
-pub use resolution::NodeModuleKind;
-pub use resolution::NodeResolution;
-pub use resolution::NodeResolutionMode;
-pub use resolution::NodeResolver;
-use resolution::NodeResolverRc;
use crate::global::global_object_middleware;
use crate::global::global_template_middleware;
@@ -149,9 +136,12 @@ impl NodePermissions for deno_permissions::PermissionsContainer {
}
#[allow(clippy::disallowed_types)]
-pub type NpmResolverRc = deno_fs::sync::MaybeArc<dyn NpmResolver>;
+pub type NpmProcessStateProviderRc =
+ deno_fs::sync::MaybeArc<dyn NpmProcessStateProvider>;
-pub trait NpmResolver: std::fmt::Debug + MaybeSend + MaybeSync {
+pub trait NpmProcessStateProvider:
+ std::fmt::Debug + MaybeSend + MaybeSync
+{
/// Gets a string containing the serialized npm state of the process.
///
/// This will be set on the `DENO_DONT_USE_INTERNAL_NODE_COMPAT_STATE` environment
@@ -161,34 +151,13 @@ pub trait NpmResolver: std::fmt::Debug + MaybeSend + MaybeSync {
// This method is only used in the CLI.
String::new()
}
+}
- /// Resolves an npm package folder path from an npm package referrer.
- fn resolve_package_folder_from_package(
- &self,
- specifier: &str,
- referrer: &ModuleSpecifier,
- ) -> Result<PathBuf, errors::PackageFolderResolveError>;
-
- fn in_npm_package(&self, specifier: &ModuleSpecifier) -> bool;
-
- fn in_npm_package_at_dir_path(&self, path: &Path) -> bool {
- let specifier =
- match ModuleSpecifier::from_directory_path(path.to_path_buf().clean()) {
- Ok(p) => p,
- Err(_) => return false,
- };
- self.in_npm_package(&specifier)
- }
-
- fn in_npm_package_at_file_path(&self, path: &Path) -> bool {
- let specifier =
- match ModuleSpecifier::from_file_path(path.to_path_buf().clean()) {
- Ok(p) => p,
- Err(_) => return false,
- };
- self.in_npm_package(&specifier)
- }
+#[allow(clippy::disallowed_types)]
+pub type NodeRequireResolverRc =
+ deno_fs::sync::MaybeArc<dyn NodeRequireResolver>;
+pub trait NodeRequireResolver: std::fmt::Debug + MaybeSend + MaybeSync {
fn ensure_read_permission(
&self,
permissions: &mut dyn NodePermissions,
@@ -223,10 +192,17 @@ fn op_node_is_promise_rejected(value: v8::Local<v8::Value>) -> bool {
#[op2]
#[string]
fn op_npm_process_state(state: &mut OpState) -> Result<String, AnyError> {
- let npm_resolver = state.borrow_mut::<NpmResolverRc>();
+ let npm_resolver = state.borrow_mut::<NpmProcessStateProviderRc>();
Ok(npm_resolver.get_npm_process_state())
}
+pub struct NodeExtInitServices {
+ pub node_require_resolver: NodeRequireResolverRc,
+ pub node_resolver: NodeResolverRc,
+ pub npm_process_state_provider: NpmProcessStateProviderRc,
+ pub npm_resolver: NpmResolverRc,
+}
+
deno_core::extension!(deno_node,
deps = [ deno_io, deno_fs ],
parameters = [P: NodePermissions],
@@ -643,21 +619,17 @@ deno_core::extension!(deno_node,
"node:zlib" = "zlib.ts",
],
options = {
- maybe_node_resolver: Option<NodeResolverRc>,
- maybe_npm_resolver: Option<NpmResolverRc>,
+ maybe_init: Option<NodeExtInitServices>,
fs: deno_fs::FileSystemRc,
},
state = |state, options| {
- // you should provide both of these or neither
- debug_assert_eq!(options.maybe_node_resolver.is_some(), options.maybe_npm_resolver.is_some());
-
state.put(options.fs.clone());
- if let Some(node_resolver) = &options.maybe_node_resolver {
- state.put(node_resolver.clone());
- }
- if let Some(npm_resolver) = &options.maybe_npm_resolver {
- state.put(npm_resolver.clone());
+ if let Some(init) = &options.maybe_init {
+ state.put(init.node_require_resolver.clone());
+ state.put(init.node_resolver.clone());
+ state.put(init.npm_resolver.clone());
+ state.put(init.npm_process_state_provider.clone());
}
},
global_template_middleware = global_template_middleware,
@@ -783,3 +755,84 @@ pub fn load_cjs_module(
js_runtime.execute_script(located_script_name!(), source_code)?;
Ok(())
}
+
+pub type NodeResolver = node_resolver::NodeResolver<DenoFsNodeResolverEnv>;
+#[allow(clippy::disallowed_types)]
+pub type NodeResolverRc =
+ deno_fs::sync::MaybeArc<node_resolver::NodeResolver<DenoFsNodeResolverEnv>>;
+
+#[derive(Debug)]
+pub struct DenoFsNodeResolverEnv {
+ fs: deno_fs::FileSystemRc,
+}
+
+impl DenoFsNodeResolverEnv {
+ pub fn new(fs: deno_fs::FileSystemRc) -> Self {
+ Self { fs }
+ }
+}
+
+impl node_resolver::env::NodeResolverEnv for DenoFsNodeResolverEnv {
+ fn is_builtin_node_module(&self, specifier: &str) -> bool {
+ is_builtin_node_module(specifier)
+ }
+
+ fn realpath_sync(
+ &self,
+ path: &std::path::Path,
+ ) -> std::io::Result<std::path::PathBuf> {
+ self
+ .fs
+ .realpath_sync(path)
+ .map_err(|err| err.into_io_error())
+ }
+
+ fn stat_sync(
+ &self,
+ path: &std::path::Path,
+ ) -> std::io::Result<node_resolver::env::NodeResolverFsStat> {
+ self
+ .fs
+ .stat_sync(path)
+ .map(|stat| node_resolver::env::NodeResolverFsStat {
+ is_file: stat.is_file,
+ is_dir: stat.is_directory,
+ is_symlink: stat.is_symlink,
+ })
+ .map_err(|err| err.into_io_error())
+ }
+
+ fn exists_sync(&self, path: &std::path::Path) -> bool {
+ self.fs.exists_sync(path)
+ }
+
+ fn pkg_json_fs(&self) -> &dyn deno_package_json::fs::DenoPkgJsonFs {
+ self
+ }
+}
+
+impl deno_package_json::fs::DenoPkgJsonFs for DenoFsNodeResolverEnv {
+ fn read_to_string_lossy(
+ &self,
+ path: &std::path::Path,
+ ) -> Result<String, std::io::Error> {
+ self
+ .fs
+ .read_text_file_lossy_sync(path, None)
+ .map_err(|err| err.into_io_error())
+ }
+}
+
+pub struct DenoPkgJsonFsAdapter<'a>(pub &'a dyn deno_fs::FileSystem);
+
+impl<'a> deno_package_json::fs::DenoPkgJsonFs for DenoPkgJsonFsAdapter<'a> {
+ fn read_to_string_lossy(
+ &self,
+ path: &Path,
+ ) -> Result<String, std::io::Error> {
+ self
+ .0
+ .read_text_file_lossy_sync(path, None)
+ .map_err(|err| err.into_io_error())
+ }
+}
diff --git a/ext/node/ops/require.rs b/ext/node/ops/require.rs
index d03b3dd9c..d074234c3 100644
--- a/ext/node/ops/require.rs
+++ b/ext/node/ops/require.rs
@@ -10,16 +10,17 @@ use deno_core::JsRuntimeInspector;
use deno_core::ModuleSpecifier;
use deno_core::OpState;
use deno_fs::FileSystemRc;
+use node_resolver::NodeModuleKind;
+use node_resolver::NodeResolutionMode;
+use node_resolver::REQUIRE_CONDITIONS;
use std::cell::RefCell;
use std::path::Path;
use std::path::PathBuf;
use std::rc::Rc;
-use crate::resolution;
-use crate::resolution::NodeResolverRc;
-use crate::NodeModuleKind;
use crate::NodePermissions;
-use crate::NodeResolutionMode;
+use crate::NodeRequireResolverRc;
+use crate::NodeResolverRc;
use crate::NpmResolverRc;
use crate::PackageJson;
@@ -30,7 +31,7 @@ fn ensure_read_permission<P>(
where
P: NodePermissions + 'static,
{
- let resolver = state.borrow::<NpmResolverRc>().clone();
+ let resolver = state.borrow::<NodeRequireResolverRc>().clone();
let permissions = state.borrow_mut::<P>();
resolver.ensure_read_permission(permissions, file_path)
}
@@ -423,7 +424,7 @@ where
exports,
Some(&referrer),
NodeModuleKind::Cjs,
- resolution::REQUIRE_CONDITIONS,
+ REQUIRE_CONDITIONS,
NodeResolutionMode::Execution,
)?;
Ok(Some(if r.scheme() == "file" {
@@ -511,7 +512,7 @@ where
exports,
Some(&referrer),
NodeModuleKind::Cjs,
- resolution::REQUIRE_CONDITIONS,
+ REQUIRE_CONDITIONS,
NodeResolutionMode::Execution,
)?;
Ok(Some(if r.scheme() == "file" {
@@ -590,7 +591,7 @@ where
Some(&referrer_url),
NodeModuleKind::Cjs,
Some(&pkg),
- resolution::REQUIRE_CONDITIONS,
+ REQUIRE_CONDITIONS,
NodeResolutionMode::Execution,
)?;
Ok(Some(url_to_file_path_string(&url)?))
diff --git a/ext/node/ops/worker_threads.rs b/ext/node/ops/worker_threads.rs
index 182ba0118..c7ea4c52c 100644
--- a/ext/node/ops/worker_threads.rs
+++ b/ext/node/ops/worker_threads.rs
@@ -6,13 +6,13 @@ use deno_core::op2;
use deno_core::url::Url;
use deno_core::OpState;
use deno_fs::FileSystemRc;
+use node_resolver::NodeResolution;
use std::path::Path;
use std::path::PathBuf;
-use crate::resolution;
-use crate::resolution::NodeResolverRc;
use crate::NodePermissions;
-use crate::NpmResolverRc;
+use crate::NodeRequireResolverRc;
+use crate::NodeResolverRc;
fn ensure_read_permission<P>(
state: &mut OpState,
@@ -21,7 +21,7 @@ fn ensure_read_permission<P>(
where
P: NodePermissions + 'static,
{
- let resolver = state.borrow::<NpmResolverRc>().clone();
+ let resolver = state.borrow::<NodeRequireResolverRc>().clone();
let permissions = state.borrow_mut::<P>();
resolver.ensure_read_permission(permissions, file_path)
}
@@ -64,9 +64,9 @@ where
}
let node_resolver = state.borrow::<NodeResolverRc>();
match node_resolver.url_to_node_resolution(url)? {
- resolution::NodeResolution::Esm(u) => Ok(u.to_string()),
- resolution::NodeResolution::CommonJs(u) => wrap_cjs(u),
- _ => Err(generic_error("Neither ESM nor CJS")),
+ NodeResolution::Esm(u) => Ok(u.to_string()),
+ NodeResolution::CommonJs(u) => wrap_cjs(u),
+ NodeResolution::BuiltIn(_) => Err(generic_error("Neither ESM nor CJS")),
}
}
diff --git a/ext/node/package_json.rs b/ext/node/package_json.rs
deleted file mode 100644
index 877acfc7a..000000000
--- a/ext/node/package_json.rs
+++ /dev/null
@@ -1,70 +0,0 @@
-// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
-
-use deno_package_json::PackageJson;
-use deno_package_json::PackageJsonRc;
-use std::cell::RefCell;
-use std::collections::HashMap;
-use std::io::ErrorKind;
-use std::path::Path;
-use std::path::PathBuf;
-
-use crate::errors::PackageJsonLoadError;
-
-// use a thread local cache so that workers have their own distinct cache
-thread_local! {
- static CACHE: RefCell<HashMap<PathBuf, PackageJsonRc>> = RefCell::new(HashMap::new());
-}
-
-pub struct PackageJsonThreadLocalCache;
-
-impl PackageJsonThreadLocalCache {
- pub fn clear() {
- CACHE.with(|cache| cache.borrow_mut().clear());
- }
-}
-
-impl deno_package_json::PackageJsonCache for PackageJsonThreadLocalCache {
- fn get(&self, path: &Path) -> Option<PackageJsonRc> {
- CACHE.with(|cache| cache.borrow().get(path).cloned())
- }
-
- fn set(&self, path: PathBuf, package_json: PackageJsonRc) {
- CACHE.with(|cache| cache.borrow_mut().insert(path, package_json));
- }
-}
-
-pub struct DenoPkgJsonFsAdapter<'a>(pub &'a dyn deno_fs::FileSystem);
-
-impl<'a> deno_package_json::fs::DenoPkgJsonFs for DenoPkgJsonFsAdapter<'a> {
- fn read_to_string_lossy(
- &self,
- path: &Path,
- ) -> Result<String, std::io::Error> {
- self
- .0
- .read_text_file_lossy_sync(path, None)
- .map_err(|err| err.into_io_error())
- }
-}
-
-/// Helper to load a package.json file using the thread local cache
-/// in deno_node.
-pub fn load_pkg_json(
- fs: &dyn deno_fs::FileSystem,
- path: &Path,
-) -> Result<Option<PackageJsonRc>, PackageJsonLoadError> {
- let result = PackageJson::load_from_path(
- path,
- &DenoPkgJsonFsAdapter(fs),
- Some(&PackageJsonThreadLocalCache),
- );
- match result {
- Ok(pkg_json) => Ok(Some(pkg_json)),
- Err(deno_package_json::PackageJsonLoadError::Io { source, .. })
- if source.kind() == ErrorKind::NotFound =>
- {
- Ok(None)
- }
- Err(err) => Err(PackageJsonLoadError(err)),
- }
-}
diff --git a/ext/node/path.rs b/ext/node/path.rs
deleted file mode 100644
index 0f151edaf..000000000
--- a/ext/node/path.rs
+++ /dev/null
@@ -1,50 +0,0 @@
-// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
-
-use std::path::Component;
-use std::path::Path;
-use std::path::PathBuf;
-
-use deno_core::ModuleSpecifier;
-
-/// Extension to path_clean::PathClean
-pub trait PathClean<T> {
- fn clean(&self) -> T;
-}
-
-impl PathClean<PathBuf> for PathBuf {
- fn clean(&self) -> PathBuf {
- let path = path_clean::PathClean::clean(self);
- if cfg!(windows) && path.to_string_lossy().contains("..\\") {
- // temporary workaround because path_clean::PathClean::clean is
- // not good enough on windows
- let mut components = Vec::new();
-
- for component in path.components() {
- match component {
- Component::CurDir => {
- // skip
- }
- Component::ParentDir => {
- let maybe_last_component = components.pop();
- if !matches!(maybe_last_component, Some(Component::Normal(_))) {
- panic!("Error normalizing: {}", path.display());
- }
- }
- Component::Normal(_) | Component::RootDir | Component::Prefix(_) => {
- components.push(component);
- }
- }
- }
- components.into_iter().collect::<PathBuf>()
- } else {
- path
- }
- }
-}
-
-pub(crate) fn to_file_specifier(path: &Path) -> ModuleSpecifier {
- match ModuleSpecifier::from_file_path(path) {
- Ok(url) => url,
- Err(_) => panic!("Invalid path: {}", path.display()),
- }
-}
diff --git a/ext/node/polyfill.rs b/ext/node/polyfill.rs
index 5847acc42..b4030a491 100644
--- a/ext/node/polyfill.rs
+++ b/ext/node/polyfill.rs
@@ -1,7 +1,5 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
-use deno_core::ModuleSpecifier;
-
/// e.g. `is_builtin_node_module("assert")`
pub fn is_builtin_node_module(module_name: &str) -> bool {
SUPPORTED_BUILTIN_NODE_MODULES
@@ -9,18 +7,6 @@ pub fn is_builtin_node_module(module_name: &str) -> bool {
.any(|m| *m == module_name)
}
-/// Ex. returns `fs` for `node:fs`
-pub fn get_module_name_from_builtin_node_module_specifier(
- specifier: &ModuleSpecifier,
-) -> Option<&str> {
- if specifier.scheme() != "node" {
- return None;
- }
-
- let (_, specifier) = specifier.as_str().split_once(':')?;
- Some(specifier)
-}
-
macro_rules! generate_builtin_node_module_lists {
($( $module_name:literal ,)+) => {
pub static SUPPORTED_BUILTIN_NODE_MODULES: &[&str] = &[
diff --git a/ext/node/resolution.rs b/ext/node/resolution.rs
deleted file mode 100644
index 6417835a2..000000000
--- a/ext/node/resolution.rs
+++ /dev/null
@@ -1,2016 +0,0 @@
-// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
-
-use std::borrow::Cow;
-use std::collections::HashMap;
-use std::path::Path;
-use std::path::PathBuf;
-
-use deno_core::anyhow::bail;
-use deno_core::error::AnyError;
-use deno_core::serde_json::Map;
-use deno_core::serde_json::Value;
-use deno_core::url::Url;
-use deno_core::ModuleSpecifier;
-use deno_fs::FileSystemRc;
-use deno_media_type::MediaType;
-use deno_package_json::PackageJsonRc;
-
-use crate::errors;
-use crate::errors::CanonicalizingPkgJsonDirError;
-use crate::errors::ClosestPkgJsonError;
-use crate::errors::DataUrlReferrerError;
-use crate::errors::FinalizeResolutionError;
-use crate::errors::InvalidModuleSpecifierError;
-use crate::errors::InvalidPackageTargetError;
-use crate::errors::LegacyResolveError;
-use crate::errors::ModuleNotFoundError;
-use crate::errors::NodeJsErrorCode;
-use crate::errors::NodeJsErrorCoded;
-use crate::errors::NodeResolveError;
-use crate::errors::NodeResolveRelativeJoinError;
-use crate::errors::PackageExportsResolveError;
-use crate::errors::PackageImportNotDefinedError;
-use crate::errors::PackageImportsResolveError;
-use crate::errors::PackageImportsResolveErrorKind;
-use crate::errors::PackageJsonLoadError;
-use crate::errors::PackagePathNotExportedError;
-use crate::errors::PackageResolveError;
-use crate::errors::PackageSubpathResolveError;
-use crate::errors::PackageSubpathResolveErrorKind;
-use crate::errors::PackageTargetNotFoundError;
-use crate::errors::PackageTargetResolveError;
-use crate::errors::PackageTargetResolveErrorKind;
-use crate::errors::ResolveBinaryCommandsError;
-use crate::errors::ResolvePkgJsonBinExportError;
-use crate::errors::ResolvePkgSubpathFromDenoModuleError;
-use crate::errors::TypeScriptNotSupportedInNpmError;
-use crate::errors::TypesNotFoundError;
-use crate::errors::TypesNotFoundErrorData;
-use crate::errors::UnsupportedDirImportError;
-use crate::errors::UnsupportedEsmUrlSchemeError;
-use crate::errors::UrlToNodeResolutionError;
-use crate::is_builtin_node_module;
-use crate::path::to_file_specifier;
-use crate::polyfill::get_module_name_from_builtin_node_module_specifier;
-use crate::NpmResolverRc;
-use crate::PackageJson;
-use crate::PathClean;
-
-pub static DEFAULT_CONDITIONS: &[&str] = &["deno", "node", "import"];
-pub static REQUIRE_CONDITIONS: &[&str] = &["require", "node"];
-static TYPES_ONLY_CONDITIONS: &[&str] = &["types"];
-
-pub type NodeModuleKind = deno_package_json::NodeModuleKind;
-
-#[derive(Debug, Clone, Copy, PartialEq, Eq)]
-pub enum NodeResolutionMode {
- Execution,
- Types,
-}
-
-impl NodeResolutionMode {
- pub fn is_types(&self) -> bool {
- matches!(self, NodeResolutionMode::Types)
- }
-}
-
-#[derive(Debug)]
-pub enum NodeResolution {
- Esm(ModuleSpecifier),
- CommonJs(ModuleSpecifier),
- BuiltIn(String),
-}
-
-impl NodeResolution {
- pub fn into_url(self) -> ModuleSpecifier {
- match self {
- Self::Esm(u) => u,
- Self::CommonJs(u) => u,
- Self::BuiltIn(specifier) => {
- if specifier.starts_with("node:") {
- ModuleSpecifier::parse(&specifier).unwrap()
- } else {
- ModuleSpecifier::parse(&format!("node:{specifier}")).unwrap()
- }
- }
- }
- }
-
- pub fn into_specifier_and_media_type(
- resolution: Option<Self>,
- ) -> (ModuleSpecifier, MediaType) {
- match resolution {
- Some(NodeResolution::CommonJs(specifier)) => {
- let media_type = MediaType::from_specifier(&specifier);
- (
- specifier,
- match media_type {
- MediaType::JavaScript | MediaType::Jsx => MediaType::Cjs,
- MediaType::TypeScript | MediaType::Tsx => MediaType::Cts,
- MediaType::Dts => MediaType::Dcts,
- _ => media_type,
- },
- )
- }
- Some(NodeResolution::Esm(specifier)) => {
- let media_type = MediaType::from_specifier(&specifier);
- (
- specifier,
- match media_type {
- MediaType::JavaScript | MediaType::Jsx => MediaType::Mjs,
- MediaType::TypeScript | MediaType::Tsx => MediaType::Mts,
- MediaType::Dts => MediaType::Dmts,
- _ => media_type,
- },
- )
- }
- Some(resolution) => (resolution.into_url(), MediaType::Dts),
- None => (
- ModuleSpecifier::parse("internal:///missing_dependency.d.ts").unwrap(),
- MediaType::Dts,
- ),
- }
- }
-}
-
-#[allow(clippy::disallowed_types)]
-pub type NodeResolverRc = deno_fs::sync::MaybeArc<NodeResolver>;
-
-#[derive(Debug)]
-pub struct NodeResolver {
- fs: FileSystemRc,
- npm_resolver: NpmResolverRc,
- in_npm_package_cache: deno_fs::sync::MaybeArcMutex<HashMap<String, bool>>,
-}
-
-impl NodeResolver {
- pub fn new(fs: FileSystemRc, npm_resolver: NpmResolverRc) -> Self {
- Self {
- fs,
- npm_resolver,
- in_npm_package_cache: deno_fs::sync::MaybeArcMutex::new(HashMap::new()),
- }
- }
-
- pub fn in_npm_package(&self, specifier: &ModuleSpecifier) -> bool {
- self.npm_resolver.in_npm_package(specifier)
- }
-
- pub fn in_npm_package_with_cache(&self, specifier: Cow<str>) -> bool {
- let mut cache = self.in_npm_package_cache.lock();
-
- if let Some(result) = cache.get(specifier.as_ref()) {
- return *result;
- }
-
- let result =
- if let Ok(specifier) = deno_core::ModuleSpecifier::parse(&specifier) {
- self.npm_resolver.in_npm_package(&specifier)
- } else {
- false
- };
- cache.insert(specifier.into_owned(), result);
- result
- }
-
- /// This function is an implementation of `defaultResolve` in
- /// `lib/internal/modules/esm/resolve.js` from Node.
- pub fn resolve(
- &self,
- specifier: &str,
- referrer: &ModuleSpecifier,
- referrer_kind: NodeModuleKind,
- mode: NodeResolutionMode,
- ) -> Result<NodeResolution, NodeResolveError> {
- // Note: if we are here, then the referrer is an esm module
- // TODO(bartlomieju): skipped "policy" part as we don't plan to support it
-
- if crate::is_builtin_node_module(specifier) {
- return Ok(NodeResolution::BuiltIn(specifier.to_string()));
- }
-
- if let Ok(url) = Url::parse(specifier) {
- if url.scheme() == "data" {
- return Ok(NodeResolution::Esm(url));
- }
-
- if let Some(module_name) =
- get_module_name_from_builtin_node_module_specifier(&url)
- {
- return Ok(NodeResolution::BuiltIn(module_name.to_string()));
- }
-
- let protocol = url.scheme();
-
- if protocol != "file" && protocol != "data" {
- return Err(
- UnsupportedEsmUrlSchemeError {
- url_scheme: protocol.to_string(),
- }
- .into(),
- );
- }
-
- // todo(dsherret): this seems wrong
- if referrer.scheme() == "data" {
- let url = referrer
- .join(specifier)
- .map_err(|source| DataUrlReferrerError { source })?;
- return Ok(NodeResolution::Esm(url));
- }
- }
-
- let url = self.module_resolve(
- specifier,
- referrer,
- referrer_kind,
- match referrer_kind {
- NodeModuleKind::Esm => DEFAULT_CONDITIONS,
- NodeModuleKind::Cjs => REQUIRE_CONDITIONS,
- },
- mode,
- )?;
-
- let url = if mode.is_types() {
- let file_path = to_file_path(&url);
- self.path_to_declaration_url(&file_path, Some(referrer), referrer_kind)?
- } else {
- url
- };
-
- let url = self.finalize_resolution(url, Some(referrer))?;
- let resolve_response = self.url_to_node_resolution(url)?;
- // TODO(bartlomieju): skipped checking errors for commonJS resolution and
- // "preserveSymlinksMain"/"preserveSymlinks" options.
- Ok(resolve_response)
- }
-
- fn module_resolve(
- &self,
- specifier: &str,
- referrer: &ModuleSpecifier,
- referrer_kind: NodeModuleKind,
- conditions: &[&str],
- mode: NodeResolutionMode,
- ) -> Result<ModuleSpecifier, NodeResolveError> {
- if should_be_treated_as_relative_or_absolute_path(specifier) {
- Ok(referrer.join(specifier).map_err(|err| {
- NodeResolveRelativeJoinError {
- path: specifier.to_string(),
- base: referrer.clone(),
- source: err,
- }
- })?)
- } else if specifier.starts_with('#') {
- let pkg_config = self
- .get_closest_package_json(referrer)
- .map_err(PackageImportsResolveErrorKind::ClosestPkgJson)
- .map_err(|err| PackageImportsResolveError(Box::new(err)))?;
- Ok(self.package_imports_resolve(
- specifier,
- Some(referrer),
- referrer_kind,
- pkg_config.as_deref(),
- conditions,
- mode,
- )?)
- } else if let Ok(resolved) = Url::parse(specifier) {
- Ok(resolved)
- } else {
- Ok(self.package_resolve(
- specifier,
- referrer,
- referrer_kind,
- conditions,
- mode,
- )?)
- }
- }
-
- fn finalize_resolution(
- &self,
- resolved: ModuleSpecifier,
- maybe_referrer: Option<&ModuleSpecifier>,
- ) -> Result<ModuleSpecifier, FinalizeResolutionError> {
- let encoded_sep_re = lazy_regex::regex!(r"%2F|%2C");
-
- if encoded_sep_re.is_match(resolved.path()) {
- return Err(
- errors::InvalidModuleSpecifierError {
- request: resolved.to_string(),
- reason: Cow::Borrowed(
- "must not include encoded \"/\" or \"\\\\\" characters",
- ),
- maybe_referrer: maybe_referrer.map(to_file_path_string),
- }
- .into(),
- );
- }
-
- if resolved.scheme() == "node" {
- return Ok(resolved);
- }
-
- let path = to_file_path(&resolved);
-
- // TODO(bartlomieju): currently not supported
- // if (getOptionValue('--experimental-specifier-resolution') === 'node') {
- // ...
- // }
-
- let p_str = path.to_str().unwrap();
- let p = if p_str.ends_with('/') {
- p_str[p_str.len() - 1..].to_string()
- } else {
- p_str.to_string()
- };
-
- let (is_dir, is_file) = if let Ok(stats) = self.fs.stat_sync(Path::new(&p))
- {
- (stats.is_directory, stats.is_file)
- } else {
- (false, false)
- };
- if is_dir {
- return Err(
- UnsupportedDirImportError {
- dir_url: resolved.clone(),
- maybe_referrer: maybe_referrer.map(ToOwned::to_owned),
- }
- .into(),
- );
- } else if !is_file {
- return Err(
- ModuleNotFoundError {
- specifier: resolved,
- maybe_referrer: maybe_referrer.map(ToOwned::to_owned),
- typ: "module",
- }
- .into(),
- );
- }
-
- Ok(resolved)
- }
-
- pub fn resolve_package_subpath_from_deno_module(
- &self,
- package_dir: &Path,
- package_subpath: Option<&str>,
- maybe_referrer: Option<&ModuleSpecifier>,
- mode: NodeResolutionMode,
- ) -> Result<NodeResolution, ResolvePkgSubpathFromDenoModuleError> {
- let node_module_kind = NodeModuleKind::Esm;
- let package_subpath = package_subpath
- .map(|s| format!("./{s}"))
- .unwrap_or_else(|| ".".to_string());
- let resolved_url = self.resolve_package_dir_subpath(
- package_dir,
- &package_subpath,
- maybe_referrer,
- node_module_kind,
- DEFAULT_CONDITIONS,
- mode,
- )?;
- let resolve_response = self.url_to_node_resolution(resolved_url)?;
- // TODO(bartlomieju): skipped checking errors for commonJS resolution and
- // "preserveSymlinksMain"/"preserveSymlinks" options.
- Ok(resolve_response)
- }
-
- pub fn resolve_binary_commands(
- &self,
- package_folder: &Path,
- ) -> Result<Vec<String>, ResolveBinaryCommandsError> {
- let pkg_json_path = package_folder.join("package.json");
- let Some(package_json) = self.load_package_json(&pkg_json_path)? else {
- return Ok(Vec::new());
- };
-
- Ok(match &package_json.bin {
- Some(Value::String(_)) => {
- let Some(name) = &package_json.name else {
- return Err(ResolveBinaryCommandsError::MissingPkgJsonName {
- pkg_json_path,
- });
- };
- vec![name.to_string()]
- }
- Some(Value::Object(o)) => {
- o.iter().map(|(key, _)| key.clone()).collect::<Vec<_>>()
- }
- _ => Vec::new(),
- })
- }
-
- pub fn resolve_binary_export(
- &self,
- package_folder: &Path,
- sub_path: Option<&str>,
- ) -> Result<NodeResolution, ResolvePkgJsonBinExportError> {
- let pkg_json_path = package_folder.join("package.json");
- let Some(package_json) = self.load_package_json(&pkg_json_path)? else {
- return Err(ResolvePkgJsonBinExportError::MissingPkgJson {
- pkg_json_path,
- });
- };
- let bin_entry =
- resolve_bin_entry_value(&package_json, sub_path).map_err(|err| {
- ResolvePkgJsonBinExportError::InvalidBinProperty {
- message: err.to_string(),
- }
- })?;
- let url = to_file_specifier(&package_folder.join(bin_entry));
-
- let resolve_response = self.url_to_node_resolution(url)?;
- // TODO(bartlomieju): skipped checking errors for commonJS resolution and
- // "preserveSymlinksMain"/"preserveSymlinks" options.
- Ok(resolve_response)
- }
-
- pub fn url_to_node_resolution(
- &self,
- url: ModuleSpecifier,
- ) -> Result<NodeResolution, UrlToNodeResolutionError> {
- let url_str = url.as_str().to_lowercase();
- if url_str.starts_with("http") || url_str.ends_with(".json") {
- Ok(NodeResolution::Esm(url))
- } else if url_str.ends_with(".js") || url_str.ends_with(".d.ts") {
- let maybe_package_config = self.get_closest_package_json(&url)?;
- match maybe_package_config {
- Some(c) if c.typ == "module" => Ok(NodeResolution::Esm(url)),
- Some(_) => Ok(NodeResolution::CommonJs(url)),
- None => Ok(NodeResolution::Esm(url)),
- }
- } else if url_str.ends_with(".mjs") || url_str.ends_with(".d.mts") {
- Ok(NodeResolution::Esm(url))
- } else if url_str.ends_with(".ts") || url_str.ends_with(".mts") {
- if self.in_npm_package(&url) {
- Err(TypeScriptNotSupportedInNpmError { specifier: url }.into())
- } else {
- Ok(NodeResolution::Esm(url))
- }
- } else {
- Ok(NodeResolution::CommonJs(url))
- }
- }
-
- /// Checks if the resolved file has a corresponding declaration file.
- fn path_to_declaration_url(
- &self,
- path: &Path,
- maybe_referrer: Option<&ModuleSpecifier>,
- referrer_kind: NodeModuleKind,
- ) -> Result<ModuleSpecifier, TypesNotFoundError> {
- fn probe_extensions(
- fs: &dyn deno_fs::FileSystem,
- path: &Path,
- lowercase_path: &str,
- referrer_kind: NodeModuleKind,
- ) -> Option<PathBuf> {
- let mut searched_for_d_mts = false;
- let mut searched_for_d_cts = false;
- if lowercase_path.ends_with(".mjs") {
- let d_mts_path = with_known_extension(path, "d.mts");
- if fs.exists_sync(&d_mts_path) {
- return Some(d_mts_path);
- }
- searched_for_d_mts = true;
- } else if lowercase_path.ends_with(".cjs") {
- let d_cts_path = with_known_extension(path, "d.cts");
- if fs.exists_sync(&d_cts_path) {
- return Some(d_cts_path);
- }
- searched_for_d_cts = true;
- }
-
- let dts_path = with_known_extension(path, "d.ts");
- if fs.exists_sync(&dts_path) {
- return Some(dts_path);
- }
-
- let specific_dts_path = match referrer_kind {
- NodeModuleKind::Cjs if !searched_for_d_cts => {
- Some(with_known_extension(path, "d.cts"))
- }
- NodeModuleKind::Esm if !searched_for_d_mts => {
- Some(with_known_extension(path, "d.mts"))
- }
- _ => None, // already searched above
- };
- if let Some(specific_dts_path) = specific_dts_path {
- if fs.exists_sync(&specific_dts_path) {
- return Some(specific_dts_path);
- }
- }
- None
- }
-
- let lowercase_path = path.to_string_lossy().to_lowercase();
- if lowercase_path.ends_with(".d.ts")
- || lowercase_path.ends_with(".d.cts")
- || lowercase_path.ends_with(".d.mts")
- {
- return Ok(to_file_specifier(path));
- }
- if let Some(path) =
- probe_extensions(&*self.fs, path, &lowercase_path, referrer_kind)
- {
- return Ok(to_file_specifier(&path));
- }
- if self.fs.is_dir_sync(path) {
- let resolution_result = self.resolve_package_dir_subpath(
- path,
- /* sub path */ ".",
- maybe_referrer,
- referrer_kind,
- match referrer_kind {
- NodeModuleKind::Esm => DEFAULT_CONDITIONS,
- NodeModuleKind::Cjs => REQUIRE_CONDITIONS,
- },
- NodeResolutionMode::Types,
- );
- if let Ok(resolution) = resolution_result {
- return Ok(resolution);
- }
- let index_path = path.join("index.js");
- if let Some(path) = probe_extensions(
- &*self.fs,
- &index_path,
- &index_path.to_string_lossy().to_lowercase(),
- referrer_kind,
- ) {
- return Ok(to_file_specifier(&path));
- }
- }
- // allow resolving .css files for types resolution
- if lowercase_path.ends_with(".css") {
- return Ok(to_file_specifier(path));
- }
- Err(TypesNotFoundError(Box::new(TypesNotFoundErrorData {
- code_specifier: to_file_specifier(path),
- maybe_referrer: maybe_referrer.cloned(),
- })))
- }
-
- #[allow(clippy::too_many_arguments)]
- pub(super) fn package_imports_resolve(
- &self,
- name: &str,
- maybe_referrer: Option<&ModuleSpecifier>,
- referrer_kind: NodeModuleKind,
- referrer_pkg_json: Option<&PackageJson>,
- conditions: &[&str],
- mode: NodeResolutionMode,
- ) -> Result<ModuleSpecifier, PackageImportsResolveError> {
- if name == "#" || name.starts_with("#/") || name.ends_with('/') {
- let reason = "is not a valid internal imports specifier name";
- return Err(
- errors::InvalidModuleSpecifierError {
- request: name.to_string(),
- reason: Cow::Borrowed(reason),
- maybe_referrer: maybe_referrer.map(to_specifier_display_string),
- }
- .into(),
- );
- }
-
- let mut package_json_path = None;
- if let Some(pkg_json) = &referrer_pkg_json {
- package_json_path = Some(pkg_json.path.clone());
- if let Some(imports) = &pkg_json.imports {
- if imports.contains_key(name) && !name.contains('*') {
- let target = imports.get(name).unwrap();
- let maybe_resolved = self.resolve_package_target(
- package_json_path.as_ref().unwrap(),
- target,
- "",
- name,
- maybe_referrer,
- referrer_kind,
- false,
- true,
- conditions,
- mode,
- )?;
- if let Some(resolved) = maybe_resolved {
- return Ok(resolved);
- }
- } else {
- let mut best_match = "";
- let mut best_match_subpath = None;
- for key in imports.keys() {
- let pattern_index = key.find('*');
- if let Some(pattern_index) = pattern_index {
- let key_sub = &key[0..=pattern_index];
- if name.starts_with(key_sub) {
- let pattern_trailer = &key[pattern_index + 1..];
- if name.len() > key.len()
- && name.ends_with(&pattern_trailer)
- && pattern_key_compare(best_match, key) == 1
- && key.rfind('*') == Some(pattern_index)
- {
- best_match = key;
- best_match_subpath = Some(
- name[pattern_index..=(name.len() - pattern_trailer.len())]
- .to_string(),
- );
- }
- }
- }
- }
-
- if !best_match.is_empty() {
- let target = imports.get(best_match).unwrap();
- let maybe_resolved = self.resolve_package_target(
- package_json_path.as_ref().unwrap(),
- target,
- &best_match_subpath.unwrap(),
- best_match,
- maybe_referrer,
- referrer_kind,
- true,
- true,
- conditions,
- mode,
- )?;
- if let Some(resolved) = maybe_resolved {
- return Ok(resolved);
- }
- }
- }
- }
- }
-
- Err(
- PackageImportNotDefinedError {
- name: name.to_string(),
- package_json_path,
- maybe_referrer: maybe_referrer.map(ToOwned::to_owned),
- }
- .into(),
- )
- }
-
- #[allow(clippy::too_many_arguments)]
- fn resolve_package_target_string(
- &self,
- target: &str,
- subpath: &str,
- match_: &str,
- package_json_path: &Path,
- maybe_referrer: Option<&ModuleSpecifier>,
- referrer_kind: NodeModuleKind,
- pattern: bool,
- internal: bool,
- conditions: &[&str],
- mode: NodeResolutionMode,
- ) -> Result<ModuleSpecifier, PackageTargetResolveError> {
- if !subpath.is_empty() && !pattern && !target.ends_with('/') {
- return Err(
- InvalidPackageTargetError {
- pkg_json_path: package_json_path.to_path_buf(),
- sub_path: match_.to_string(),
- target: target.to_string(),
- is_import: internal,
- maybe_referrer: maybe_referrer.map(ToOwned::to_owned),
- }
- .into(),
- );
- }
- let invalid_segment_re =
- lazy_regex::regex!(r"(^|\\|/)(\.\.?|node_modules)(\\|/|$)");
- let pattern_re = lazy_regex::regex!(r"\*");
- if !target.starts_with("./") {
- if internal && !target.starts_with("../") && !target.starts_with('/') {
- let target_url = Url::parse(target);
- match target_url {
- Ok(url) => {
- if get_module_name_from_builtin_node_module_specifier(&url)
- .is_some()
- {
- return Ok(url);
- }
- }
- Err(_) => {
- let export_target = if pattern {
- pattern_re
- .replace(target, |_caps: &regex::Captures| subpath)
- .to_string()
- } else {
- format!("{target}{subpath}")
- };
- let package_json_url = to_file_specifier(package_json_path);
- let result = match self.package_resolve(
- &export_target,
- &package_json_url,
- referrer_kind,
- conditions,
- mode,
- ) {
- Ok(url) => Ok(url),
- Err(err) => match err.code() {
- NodeJsErrorCode::ERR_INVALID_MODULE_SPECIFIER
- | NodeJsErrorCode::ERR_INVALID_PACKAGE_CONFIG
- | NodeJsErrorCode::ERR_INVALID_PACKAGE_TARGET
- | NodeJsErrorCode::ERR_PACKAGE_IMPORT_NOT_DEFINED
- | NodeJsErrorCode::ERR_PACKAGE_PATH_NOT_EXPORTED
- | NodeJsErrorCode::ERR_UNKNOWN_FILE_EXTENSION
- | NodeJsErrorCode::ERR_UNSUPPORTED_DIR_IMPORT
- | NodeJsErrorCode::ERR_UNSUPPORTED_ESM_URL_SCHEME
- | NodeJsErrorCode::ERR_TYPES_NOT_FOUND => {
- Err(PackageTargetResolveErrorKind::PackageResolve(err).into())
- }
- NodeJsErrorCode::ERR_MODULE_NOT_FOUND => Err(
- PackageTargetResolveErrorKind::NotFound(
- PackageTargetNotFoundError {
- pkg_json_path: package_json_path.to_path_buf(),
- target: export_target.to_string(),
- maybe_referrer: maybe_referrer.map(ToOwned::to_owned),
- referrer_kind,
- mode,
- },
- )
- .into(),
- ),
- },
- };
-
- return match result {
- Ok(url) => Ok(url),
- Err(err) => {
- if is_builtin_node_module(target) {
- Ok(
- ModuleSpecifier::parse(&format!("node:{}", target))
- .unwrap(),
- )
- } else {
- Err(err)
- }
- }
- };
- }
- }
- }
- return Err(
- InvalidPackageTargetError {
- pkg_json_path: package_json_path.to_path_buf(),
- sub_path: match_.to_string(),
- target: target.to_string(),
- is_import: internal,
- maybe_referrer: maybe_referrer.map(ToOwned::to_owned),
- }
- .into(),
- );
- }
- if invalid_segment_re.is_match(&target[2..]) {
- return Err(
- InvalidPackageTargetError {
- pkg_json_path: package_json_path.to_path_buf(),
- sub_path: match_.to_string(),
- target: target.to_string(),
- is_import: internal,
- maybe_referrer: maybe_referrer.map(ToOwned::to_owned),
- }
- .into(),
- );
- }
- let package_path = package_json_path.parent().unwrap();
- let resolved_path = package_path.join(target).clean();
- if !resolved_path.starts_with(package_path) {
- return Err(
- InvalidPackageTargetError {
- pkg_json_path: package_json_path.to_path_buf(),
- sub_path: match_.to_string(),
- target: target.to_string(),
- is_import: internal,
- maybe_referrer: maybe_referrer.map(ToOwned::to_owned),
- }
- .into(),
- );
- }
- if subpath.is_empty() {
- return Ok(to_file_specifier(&resolved_path));
- }
- if invalid_segment_re.is_match(subpath) {
- let request = if pattern {
- match_.replace('*', subpath)
- } else {
- format!("{match_}{subpath}")
- };
- return Err(
- throw_invalid_subpath(
- request,
- package_json_path,
- internal,
- maybe_referrer,
- )
- .into(),
- );
- }
- if pattern {
- let resolved_path_str = resolved_path.to_string_lossy();
- let replaced = pattern_re
- .replace(&resolved_path_str, |_caps: &regex::Captures| subpath);
- return Ok(to_file_specifier(&PathBuf::from(replaced.to_string())));
- }
- Ok(to_file_specifier(&resolved_path.join(subpath).clean()))
- }
-
- #[allow(clippy::too_many_arguments)]
- fn resolve_package_target(
- &self,
- package_json_path: &Path,
- target: &Value,
- subpath: &str,
- package_subpath: &str,
- maybe_referrer: Option<&ModuleSpecifier>,
- referrer_kind: NodeModuleKind,
- pattern: bool,
- internal: bool,
- conditions: &[&str],
- mode: NodeResolutionMode,
- ) -> Result<Option<ModuleSpecifier>, PackageTargetResolveError> {
- let result = self.resolve_package_target_inner(
- package_json_path,
- target,
- subpath,
- package_subpath,
- maybe_referrer,
- referrer_kind,
- pattern,
- internal,
- conditions,
- mode,
- );
- match result {
- Ok(maybe_resolved) => Ok(maybe_resolved),
- Err(err) => {
- if mode.is_types()
- && err.code() == NodeJsErrorCode::ERR_TYPES_NOT_FOUND
- && conditions != TYPES_ONLY_CONDITIONS
- {
- // try resolving with just "types" conditions for when someone misconfigures
- // and puts the "types" condition in the wrong place
- if let Ok(Some(resolved)) = self.resolve_package_target_inner(
- package_json_path,
- target,
- subpath,
- package_subpath,
- maybe_referrer,
- referrer_kind,
- pattern,
- internal,
- TYPES_ONLY_CONDITIONS,
- mode,
- ) {
- return Ok(Some(resolved));
- }
- }
-
- Err(err)
- }
- }
- }
-
- #[allow(clippy::too_many_arguments)]
- fn resolve_package_target_inner(
- &self,
- package_json_path: &Path,
- target: &Value,
- subpath: &str,
- package_subpath: &str,
- maybe_referrer: Option<&ModuleSpecifier>,
- referrer_kind: NodeModuleKind,
- pattern: bool,
- internal: bool,
- conditions: &[&str],
- mode: NodeResolutionMode,
- ) -> Result<Option<ModuleSpecifier>, PackageTargetResolveError> {
- if let Some(target) = target.as_str() {
- let url = self.resolve_package_target_string(
- target,
- subpath,
- package_subpath,
- package_json_path,
- maybe_referrer,
- referrer_kind,
- pattern,
- internal,
- conditions,
- mode,
- )?;
- if mode.is_types() && url.scheme() == "file" {
- let path = url.to_file_path().unwrap();
- return Ok(Some(self.path_to_declaration_url(
- &path,
- maybe_referrer,
- referrer_kind,
- )?));
- } else {
- return Ok(Some(url));
- }
- } else if let Some(target_arr) = target.as_array() {
- if target_arr.is_empty() {
- return Ok(None);
- }
-
- let mut last_error = None;
- for target_item in target_arr {
- let resolved_result = self.resolve_package_target(
- package_json_path,
- target_item,
- subpath,
- package_subpath,
- maybe_referrer,
- referrer_kind,
- pattern,
- internal,
- conditions,
- mode,
- );
-
- match resolved_result {
- Ok(Some(resolved)) => return Ok(Some(resolved)),
- Ok(None) => {
- last_error = None;
- continue;
- }
- Err(e) => {
- if e.code() == NodeJsErrorCode::ERR_INVALID_PACKAGE_TARGET {
- last_error = Some(e);
- continue;
- } else {
- return Err(e);
- }
- }
- }
- }
- if last_error.is_none() {
- return Ok(None);
- }
- return Err(last_error.unwrap());
- } else if let Some(target_obj) = target.as_object() {
- for key in target_obj.keys() {
- // TODO(bartlomieju): verify that keys are not numeric
- // return Err(errors::err_invalid_package_config(
- // to_file_path_string(package_json_url),
- // Some(base.as_str().to_string()),
- // Some("\"exports\" cannot contain numeric property keys.".to_string()),
- // ));
-
- if key == "default"
- || conditions.contains(&key.as_str())
- || mode.is_types() && key.as_str() == "types"
- {
- let condition_target = target_obj.get(key).unwrap();
-
- let resolved = self.resolve_package_target(
- package_json_path,
- condition_target,
- subpath,
- package_subpath,
- maybe_referrer,
- referrer_kind,
- pattern,
- internal,
- conditions,
- mode,
- )?;
- match resolved {
- Some(resolved) => return Ok(Some(resolved)),
- None => {
- continue;
- }
- }
- }
- }
- } else if target.is_null() {
- return Ok(None);
- }
-
- Err(
- InvalidPackageTargetError {
- pkg_json_path: package_json_path.to_path_buf(),
- sub_path: package_subpath.to_string(),
- target: target.to_string(),
- is_import: internal,
- maybe_referrer: maybe_referrer.map(ToOwned::to_owned),
- }
- .into(),
- )
- }
-
- #[allow(clippy::too_many_arguments)]
- pub fn package_exports_resolve(
- &self,
- package_json_path: &Path,
- package_subpath: &str,
- package_exports: &Map<String, Value>,
- maybe_referrer: Option<&ModuleSpecifier>,
- referrer_kind: NodeModuleKind,
- conditions: &[&str],
- mode: NodeResolutionMode,
- ) -> Result<ModuleSpecifier, PackageExportsResolveError> {
- if package_exports.contains_key(package_subpath)
- && package_subpath.find('*').is_none()
- && !package_subpath.ends_with('/')
- {
- let target = package_exports.get(package_subpath).unwrap();
- let resolved = self.resolve_package_target(
- package_json_path,
- target,
- "",
- package_subpath,
- maybe_referrer,
- referrer_kind,
- false,
- false,
- conditions,
- mode,
- )?;
- return match resolved {
- Some(resolved) => Ok(resolved),
- None => Err(
- PackagePathNotExportedError {
- pkg_json_path: package_json_path.to_path_buf(),
- subpath: package_subpath.to_string(),
- maybe_referrer: maybe_referrer.map(ToOwned::to_owned),
- mode,
- }
- .into(),
- ),
- };
- }
-
- let mut best_match = "";
- let mut best_match_subpath = None;
- for key in package_exports.keys() {
- let pattern_index = key.find('*');
- if let Some(pattern_index) = pattern_index {
- let key_sub = &key[0..pattern_index];
- if package_subpath.starts_with(key_sub) {
- // When this reaches EOL, this can throw at the top of the whole function:
- //
- // if (StringPrototypeEndsWith(packageSubpath, '/'))
- // throwInvalidSubpath(packageSubpath)
- //
- // To match "imports" and the spec.
- if package_subpath.ends_with('/') {
- // TODO(bartlomieju):
- // emitTrailingSlashPatternDeprecation();
- }
- let pattern_trailer = &key[pattern_index + 1..];
- if package_subpath.len() >= key.len()
- && package_subpath.ends_with(&pattern_trailer)
- && pattern_key_compare(best_match, key) == 1
- && key.rfind('*') == Some(pattern_index)
- {
- best_match = key;
- best_match_subpath = Some(
- package_subpath[pattern_index
- ..(package_subpath.len() - pattern_trailer.len())]
- .to_string(),
- );
- }
- }
- }
- }
-
- if !best_match.is_empty() {
- let target = package_exports.get(best_match).unwrap();
- let maybe_resolved = self.resolve_package_target(
- package_json_path,
- target,
- &best_match_subpath.unwrap(),
- best_match,
- maybe_referrer,
- referrer_kind,
- true,
- false,
- conditions,
- mode,
- )?;
- if let Some(resolved) = maybe_resolved {
- return Ok(resolved);
- } else {
- return Err(
- PackagePathNotExportedError {
- pkg_json_path: package_json_path.to_path_buf(),
- subpath: package_subpath.to_string(),
- maybe_referrer: maybe_referrer.map(ToOwned::to_owned),
- mode,
- }
- .into(),
- );
- }
- }
-
- Err(
- PackagePathNotExportedError {
- pkg_json_path: package_json_path.to_path_buf(),
- subpath: package_subpath.to_string(),
- maybe_referrer: maybe_referrer.map(ToOwned::to_owned),
- mode,
- }
- .into(),
- )
- }
-
- pub(super) fn package_resolve(
- &self,
- specifier: &str,
- referrer: &ModuleSpecifier,
- referrer_kind: NodeModuleKind,
- conditions: &[&str],
- mode: NodeResolutionMode,
- ) -> Result<ModuleSpecifier, PackageResolveError> {
- let (package_name, package_subpath, _is_scoped) =
- parse_npm_pkg_name(specifier, referrer)?;
-
- if let Some(package_config) = self.get_closest_package_json(referrer)? {
- // ResolveSelf
- if package_config.name.as_ref() == Some(&package_name) {
- if let Some(exports) = &package_config.exports {
- return self
- .package_exports_resolve(
- &package_config.path,
- &package_subpath,
- exports,
- Some(referrer),
- referrer_kind,
- conditions,
- mode,
- )
- .map_err(|err| err.into());
- }
- }
- }
-
- self.resolve_package_subpath_for_package(
- &package_name,
- &package_subpath,
- referrer,
- referrer_kind,
- conditions,
- mode,
- )
- }
-
- #[allow(clippy::too_many_arguments)]
- fn resolve_package_subpath_for_package(
- &self,
- package_name: &str,
- package_subpath: &str,
- referrer: &ModuleSpecifier,
- referrer_kind: NodeModuleKind,
- conditions: &[&str],
- mode: NodeResolutionMode,
- ) -> Result<ModuleSpecifier, PackageResolveError> {
- let result = self.resolve_package_subpath_for_package_inner(
- package_name,
- package_subpath,
- referrer,
- referrer_kind,
- conditions,
- mode,
- );
- if mode.is_types() && !matches!(result, Ok(ModuleSpecifier { .. })) {
- // try to resolve with the @types package
- let package_name = types_package_name(package_name);
- if let Ok(result) = self.resolve_package_subpath_for_package_inner(
- &package_name,
- package_subpath,
- referrer,
- referrer_kind,
- conditions,
- mode,
- ) {
- return Ok(result);
- }
- }
- result
- }
-
- #[allow(clippy::too_many_arguments)]
- fn resolve_package_subpath_for_package_inner(
- &self,
- package_name: &str,
- package_subpath: &str,
- referrer: &ModuleSpecifier,
- referrer_kind: NodeModuleKind,
- conditions: &[&str],
- mode: NodeResolutionMode,
- ) -> Result<ModuleSpecifier, PackageResolveError> {
- let package_dir_path = self
- .npm_resolver
- .resolve_package_folder_from_package(package_name, referrer)?;
-
- // todo: error with this instead when can't find package
- // Err(errors::err_module_not_found(
- // &package_json_url
- // .join(".")
- // .unwrap()
- // .to_file_path()
- // .unwrap()
- // .display()
- // .to_string(),
- // &to_file_path_string(referrer),
- // "package",
- // ))
-
- // Package match.
- self
- .resolve_package_dir_subpath(
- &package_dir_path,
- package_subpath,
- Some(referrer),
- referrer_kind,
- conditions,
- mode,
- )
- .map_err(|err| err.into())
- }
-
- #[allow(clippy::too_many_arguments)]
- fn resolve_package_dir_subpath(
- &self,
- package_dir_path: &Path,
- package_subpath: &str,
- maybe_referrer: Option<&ModuleSpecifier>,
- referrer_kind: NodeModuleKind,
- conditions: &[&str],
- mode: NodeResolutionMode,
- ) -> Result<ModuleSpecifier, PackageSubpathResolveError> {
- let package_json_path = package_dir_path.join("package.json");
- match self.load_package_json(&package_json_path)? {
- Some(pkg_json) => self.resolve_package_subpath(
- &pkg_json,
- package_subpath,
- maybe_referrer,
- referrer_kind,
- conditions,
- mode,
- ),
- None => self
- .resolve_package_subpath_no_pkg_json(
- package_dir_path,
- package_subpath,
- maybe_referrer,
- referrer_kind,
- mode,
- )
- .map_err(|err| {
- PackageSubpathResolveErrorKind::LegacyResolve(err).into()
- }),
- }
- }
-
- #[allow(clippy::too_many_arguments)]
- fn resolve_package_subpath(
- &self,
- package_json: &PackageJson,
- package_subpath: &str,
- referrer: Option<&ModuleSpecifier>,
- referrer_kind: NodeModuleKind,
- conditions: &[&str],
- mode: NodeResolutionMode,
- ) -> Result<ModuleSpecifier, PackageSubpathResolveError> {
- if let Some(exports) = &package_json.exports {
- let result = self.package_exports_resolve(
- &package_json.path,
- package_subpath,
- exports,
- referrer,
- referrer_kind,
- conditions,
- mode,
- );
- match result {
- Ok(found) => return Ok(found),
- Err(exports_err) => {
- if mode.is_types() && package_subpath == "." {
- return self
- .legacy_main_resolve(package_json, referrer, referrer_kind, mode)
- .map_err(|err| {
- PackageSubpathResolveErrorKind::LegacyResolve(err).into()
- });
- }
- return Err(
- PackageSubpathResolveErrorKind::Exports(exports_err).into(),
- );
- }
- }
- }
-
- if package_subpath == "." {
- return self
- .legacy_main_resolve(package_json, referrer, referrer_kind, mode)
- .map_err(|err| {
- PackageSubpathResolveErrorKind::LegacyResolve(err).into()
- });
- }
-
- self
- .resolve_subpath_exact(
- package_json.path.parent().unwrap(),
- package_subpath,
- referrer,
- referrer_kind,
- mode,
- )
- .map_err(|err| {
- PackageSubpathResolveErrorKind::LegacyResolve(err.into()).into()
- })
- }
-
- fn resolve_subpath_exact(
- &self,
- directory: &Path,
- package_subpath: &str,
- referrer: Option<&ModuleSpecifier>,
- referrer_kind: NodeModuleKind,
- mode: NodeResolutionMode,
- ) -> Result<ModuleSpecifier, TypesNotFoundError> {
- assert_ne!(package_subpath, ".");
- let file_path = directory.join(package_subpath);
- if mode.is_types() {
- Ok(self.path_to_declaration_url(&file_path, referrer, referrer_kind)?)
- } else {
- Ok(to_file_specifier(&file_path))
- }
- }
-
- fn resolve_package_subpath_no_pkg_json(
- &self,
- directory: &Path,
- package_subpath: &str,
- maybe_referrer: Option<&ModuleSpecifier>,
- referrer_kind: NodeModuleKind,
- mode: NodeResolutionMode,
- ) -> Result<ModuleSpecifier, LegacyResolveError> {
- if package_subpath == "." {
- self.legacy_index_resolve(directory, maybe_referrer, referrer_kind, mode)
- } else {
- self
- .resolve_subpath_exact(
- directory,
- package_subpath,
- maybe_referrer,
- referrer_kind,
- mode,
- )
- .map_err(|err| err.into())
- }
- }
-
- pub fn get_closest_package_json(
- &self,
- url: &ModuleSpecifier,
- ) -> Result<Option<PackageJsonRc>, ClosestPkgJsonError> {
- let Ok(file_path) = url.to_file_path() else {
- return Ok(None);
- };
- self.get_closest_package_json_from_path(&file_path)
- }
-
- pub fn get_closest_package_json_from_path(
- &self,
- file_path: &Path,
- ) -> Result<Option<PackageJsonRc>, ClosestPkgJsonError> {
- let parent_dir = file_path.parent().unwrap();
- let current_dir =
- deno_core::strip_unc_prefix(self.fs.realpath_sync(parent_dir).map_err(
- |source| CanonicalizingPkgJsonDirError {
- dir_path: parent_dir.to_path_buf(),
- source: source.into_io_error(),
- },
- )?);
- for current_dir in current_dir.ancestors() {
- let package_json_path = current_dir.join("package.json");
- if let Some(pkg_json) = self.load_package_json(&package_json_path)? {
- return Ok(Some(pkg_json));
- }
- }
-
- Ok(None)
- }
-
- pub(super) fn load_package_json(
- &self,
- package_json_path: &Path,
- ) -> Result<Option<PackageJsonRc>, PackageJsonLoadError> {
- crate::package_json::load_pkg_json(&*self.fs, package_json_path)
- }
-
- pub(super) fn legacy_main_resolve(
- &self,
- package_json: &PackageJson,
- maybe_referrer: Option<&ModuleSpecifier>,
- referrer_kind: NodeModuleKind,
- mode: NodeResolutionMode,
- ) -> Result<ModuleSpecifier, LegacyResolveError> {
- let maybe_main = if mode.is_types() {
- match package_json.types.as_ref() {
- Some(types) => Some(types.as_str()),
- None => {
- // fallback to checking the main entrypoint for
- // a corresponding declaration file
- if let Some(main) = package_json.main(referrer_kind) {
- let main = package_json.path.parent().unwrap().join(main).clean();
- let decl_url_result = self.path_to_declaration_url(
- &main,
- maybe_referrer,
- referrer_kind,
- );
- // don't surface errors, fallback to checking the index now
- if let Ok(url) = decl_url_result {
- return Ok(url);
- }
- }
- None
- }
- }
- } else {
- package_json.main(referrer_kind)
- };
-
- if let Some(main) = maybe_main {
- let guess = package_json.path.parent().unwrap().join(main).clean();
- if self.fs.is_file_sync(&guess) {
- return Ok(to_file_specifier(&guess));
- }
-
- // todo(dsherret): investigate exactly how node and typescript handles this
- let endings = if mode.is_types() {
- match referrer_kind {
- NodeModuleKind::Cjs => {
- vec![".d.ts", ".d.cts", "/index.d.ts", "/index.d.cts"]
- }
- NodeModuleKind::Esm => vec![
- ".d.ts",
- ".d.mts",
- "/index.d.ts",
- "/index.d.mts",
- ".d.cts",
- "/index.d.cts",
- ],
- }
- } else {
- vec![".js", "/index.js"]
- };
- for ending in endings {
- let guess = package_json
- .path
- .parent()
- .unwrap()
- .join(format!("{main}{ending}"))
- .clean();
- if self.fs.is_file_sync(&guess) {
- // TODO(bartlomieju): emitLegacyIndexDeprecation()
- return Ok(to_file_specifier(&guess));
- }
- }
- }
-
- self.legacy_index_resolve(
- package_json.path.parent().unwrap(),
- maybe_referrer,
- referrer_kind,
- mode,
- )
- }
-
- fn legacy_index_resolve(
- &self,
- directory: &Path,
- maybe_referrer: Option<&ModuleSpecifier>,
- referrer_kind: NodeModuleKind,
- mode: NodeResolutionMode,
- ) -> Result<ModuleSpecifier, LegacyResolveError> {
- let index_file_names = if mode.is_types() {
- // todo(dsherret): investigate exactly how typescript does this
- match referrer_kind {
- NodeModuleKind::Cjs => vec!["index.d.ts", "index.d.cts"],
- NodeModuleKind::Esm => vec!["index.d.ts", "index.d.mts", "index.d.cts"],
- }
- } else {
- vec!["index.js"]
- };
- for index_file_name in index_file_names {
- let guess = directory.join(index_file_name).clean();
- if self.fs.is_file_sync(&guess) {
- // TODO(bartlomieju): emitLegacyIndexDeprecation()
- return Ok(to_file_specifier(&guess));
- }
- }
-
- if mode.is_types() {
- Err(
- TypesNotFoundError(Box::new(TypesNotFoundErrorData {
- code_specifier: to_file_specifier(&directory.join("index.js")),
- maybe_referrer: maybe_referrer.cloned(),
- }))
- .into(),
- )
- } else {
- Err(
- ModuleNotFoundError {
- specifier: to_file_specifier(&directory.join("index.js")),
- typ: "module",
- maybe_referrer: maybe_referrer.cloned(),
- }
- .into(),
- )
- }
- }
-}
-
-fn resolve_bin_entry_value<'a>(
- package_json: &'a PackageJson,
- bin_name: Option<&str>,
-) -> Result<&'a str, AnyError> {
- let bin = match &package_json.bin {
- Some(bin) => bin,
- None => bail!(
- "'{}' did not have a bin property",
- package_json.path.display(),
- ),
- };
- let bin_entry = match bin {
- Value::String(_) => {
- if bin_name.is_some()
- && bin_name
- != package_json
- .name
- .as_deref()
- .map(|name| name.rsplit_once('/').map_or(name, |(_, name)| name))
- {
- None
- } else {
- Some(bin)
- }
- }
- Value::Object(o) => {
- if let Some(bin_name) = bin_name {
- o.get(bin_name)
- } else if o.len() == 1
- || o.len() > 1 && o.values().all(|v| v == o.values().next().unwrap())
- {
- o.values().next()
- } else {
- package_json.name.as_ref().and_then(|n| o.get(n))
- }
- }
- _ => bail!(
- "'{}' did not have a bin property with a string or object value",
- package_json.path.display()
- ),
- };
- let bin_entry = match bin_entry {
- Some(e) => e,
- None => {
- let prefix = package_json
- .name
- .as_ref()
- .map(|n| {
- let mut prefix = format!("npm:{}", n);
- if let Some(version) = &package_json.version {
- prefix.push('@');
- prefix.push_str(version);
- }
- prefix.push('/');
- prefix
- })
- .unwrap_or_default();
- let keys = bin
- .as_object()
- .map(|o| {
- o.keys()
- .map(|k| format!(" * {prefix}{k}"))
- .collect::<Vec<_>>()
- })
- .unwrap_or_default();
- bail!(
- "'{}' did not have a bin entry{}{}",
- package_json.path.display(),
- bin_name
- .or(package_json.name.as_deref())
- .map(|name| format!(" for '{}'", name))
- .unwrap_or_default(),
- if keys.is_empty() {
- "".to_string()
- } else {
- format!("\n\nPossibilities:\n{}", keys.join("\n"))
- }
- )
- }
- };
- match bin_entry {
- Value::String(s) => Ok(s),
- _ => bail!(
- "'{}' had a non-string sub property of bin",
- package_json.path.display(),
- ),
- }
-}
-
-fn to_file_path(url: &ModuleSpecifier) -> PathBuf {
- url
- .to_file_path()
- .unwrap_or_else(|_| panic!("Provided URL was not file:// URL: {url}"))
-}
-
-fn to_file_path_string(url: &ModuleSpecifier) -> String {
- to_file_path(url).display().to_string()
-}
-
-fn should_be_treated_as_relative_or_absolute_path(specifier: &str) -> bool {
- if specifier.is_empty() {
- return false;
- }
-
- if specifier.starts_with('/') {
- return true;
- }
-
- is_relative_specifier(specifier)
-}
-
-// TODO(ry) We very likely have this utility function elsewhere in Deno.
-fn is_relative_specifier(specifier: &str) -> bool {
- let specifier_len = specifier.len();
- let specifier_chars: Vec<_> = specifier.chars().take(3).collect();
-
- if !specifier_chars.is_empty() && specifier_chars[0] == '.' {
- if specifier_len == 1 || specifier_chars[1] == '/' {
- return true;
- }
- if specifier_chars[1] == '.'
- && (specifier_len == 2 || specifier_chars[2] == '/')
- {
- return true;
- }
- }
- false
-}
-
-/// Alternate `PathBuf::with_extension` that will handle known extensions
-/// more intelligently.
-fn with_known_extension(path: &Path, ext: &str) -> PathBuf {
- const NON_DECL_EXTS: &[&str] = &[
- "cjs", "js", "json", "jsx", "mjs", "tsx", /* ex. types.d */ "d",
- ];
- const DECL_EXTS: &[&str] = &["cts", "mts", "ts"];
-
- let file_name = match path.file_name() {
- Some(value) => value.to_string_lossy(),
- None => return path.to_path_buf(),
- };
- let lowercase_file_name = file_name.to_lowercase();
- let period_index = lowercase_file_name.rfind('.').and_then(|period_index| {
- let ext = &lowercase_file_name[period_index + 1..];
- if DECL_EXTS.contains(&ext) {
- if let Some(next_period_index) =
- lowercase_file_name[..period_index].rfind('.')
- {
- if &lowercase_file_name[next_period_index + 1..period_index] == "d" {
- Some(next_period_index)
- } else {
- Some(period_index)
- }
- } else {
- Some(period_index)
- }
- } else if NON_DECL_EXTS.contains(&ext) {
- Some(period_index)
- } else {
- None
- }
- });
-
- let file_name = match period_index {
- Some(period_index) => &file_name[..period_index],
- None => &file_name,
- };
- path.with_file_name(format!("{file_name}.{ext}"))
-}
-
-fn to_specifier_display_string(url: &ModuleSpecifier) -> String {
- if let Ok(path) = url.to_file_path() {
- path.display().to_string()
- } else {
- url.to_string()
- }
-}
-
-fn throw_invalid_subpath(
- subpath: String,
- package_json_path: &Path,
- internal: bool,
- maybe_referrer: Option<&ModuleSpecifier>,
-) -> InvalidModuleSpecifierError {
- let ie = if internal { "imports" } else { "exports" };
- let reason = format!(
- "request is not a valid subpath for the \"{}\" resolution of {}",
- ie,
- package_json_path.display(),
- );
- InvalidModuleSpecifierError {
- request: subpath,
- reason: Cow::Owned(reason),
- maybe_referrer: maybe_referrer.map(to_specifier_display_string),
- }
-}
-
-pub fn parse_npm_pkg_name(
- specifier: &str,
- referrer: &ModuleSpecifier,
-) -> Result<(String, String, bool), InvalidModuleSpecifierError> {
- let mut separator_index = specifier.find('/');
- let mut valid_package_name = true;
- let mut is_scoped = false;
- if specifier.is_empty() {
- valid_package_name = false;
- } else if specifier.starts_with('@') {
- is_scoped = true;
- if let Some(index) = separator_index {
- separator_index = specifier[index + 1..]
- .find('/')
- .map(|new_index| index + 1 + new_index);
- } else {
- valid_package_name = false;
- }
- }
-
- let package_name = if let Some(index) = separator_index {
- specifier[0..index].to_string()
- } else {
- specifier.to_string()
- };
-
- // Package name cannot have leading . and cannot have percent-encoding or separators.
- for ch in package_name.chars() {
- if ch == '%' || ch == '\\' {
- valid_package_name = false;
- break;
- }
- }
-
- if !valid_package_name {
- return Err(errors::InvalidModuleSpecifierError {
- request: specifier.to_string(),
- reason: Cow::Borrowed("is not a valid package name"),
- maybe_referrer: Some(to_specifier_display_string(referrer)),
- });
- }
-
- let package_subpath = if let Some(index) = separator_index {
- format!(".{}", specifier.chars().skip(index).collect::<String>())
- } else {
- ".".to_string()
- };
-
- Ok((package_name, package_subpath, is_scoped))
-}
-
-fn pattern_key_compare(a: &str, b: &str) -> i32 {
- let a_pattern_index = a.find('*');
- let b_pattern_index = b.find('*');
-
- let base_len_a = if let Some(index) = a_pattern_index {
- index + 1
- } else {
- a.len()
- };
- let base_len_b = if let Some(index) = b_pattern_index {
- index + 1
- } else {
- b.len()
- };
-
- if base_len_a > base_len_b {
- return -1;
- }
-
- if base_len_b > base_len_a {
- return 1;
- }
-
- if a_pattern_index.is_none() {
- return 1;
- }
-
- if b_pattern_index.is_none() {
- return -1;
- }
-
- if a.len() > b.len() {
- return -1;
- }
-
- if b.len() > a.len() {
- return 1;
- }
-
- 0
-}
-
-/// Gets the corresponding @types package for the provided package name.
-fn types_package_name(package_name: &str) -> String {
- debug_assert!(!package_name.starts_with("@types/"));
- // Scoped packages will get two underscores for each slash
- // https://github.com/DefinitelyTyped/DefinitelyTyped/tree/15f1ece08f7b498f4b9a2147c2a46e94416ca777#what-about-scoped-packages
- format!("@types/{}", package_name.replace('/', "__"))
-}
-
-#[cfg(test)]
-mod tests {
- use deno_core::serde_json::json;
-
- use super::*;
-
- fn build_package_json(json: Value) -> PackageJson {
- PackageJson::load_from_value(PathBuf::from("/package.json"), json)
- }
-
- #[test]
- fn test_resolve_bin_entry_value() {
- // should resolve the specified value
- let pkg_json = build_package_json(json!({
- "name": "pkg",
- "version": "1.1.1",
- "bin": {
- "bin1": "./value1",
- "bin2": "./value2",
- "pkg": "./value3",
- }
- }));
- assert_eq!(
- resolve_bin_entry_value(&pkg_json, Some("bin1")).unwrap(),
- "./value1"
- );
-
- // should resolve the value with the same name when not specified
- assert_eq!(
- resolve_bin_entry_value(&pkg_json, None).unwrap(),
- "./value3"
- );
-
- // should not resolve when specified value does not exist
- assert_eq!(
- resolve_bin_entry_value(&pkg_json, Some("other"),)
- .err()
- .unwrap()
- .to_string(),
- concat!(
- "'/package.json' did not have a bin entry for 'other'\n",
- "\n",
- "Possibilities:\n",
- " * npm:pkg@1.1.1/bin1\n",
- " * npm:pkg@1.1.1/bin2\n",
- " * npm:pkg@1.1.1/pkg"
- )
- );
-
- // should not resolve when default value can't be determined
- let pkg_json = build_package_json(json!({
- "name": "pkg",
- "version": "1.1.1",
- "bin": {
- "bin": "./value1",
- "bin2": "./value2",
- }
- }));
- assert_eq!(
- resolve_bin_entry_value(&pkg_json, None)
- .err()
- .unwrap()
- .to_string(),
- concat!(
- "'/package.json' did not have a bin entry for 'pkg'\n",
- "\n",
- "Possibilities:\n",
- " * npm:pkg@1.1.1/bin\n",
- " * npm:pkg@1.1.1/bin2",
- )
- );
-
- // should resolve since all the values are the same
- let pkg_json = build_package_json(json!({
- "name": "pkg",
- "version": "1.2.3",
- "bin": {
- "bin1": "./value",
- "bin2": "./value",
- }
- }));
- assert_eq!(
- resolve_bin_entry_value(&pkg_json, None,).unwrap(),
- "./value"
- );
-
- // should not resolve when specified and is a string
- let pkg_json = build_package_json(json!({
- "name": "pkg",
- "version": "1.2.3",
- "bin": "./value",
- }));
- assert_eq!(
- resolve_bin_entry_value(&pkg_json, Some("path"),)
- .err()
- .unwrap()
- .to_string(),
- "'/package.json' did not have a bin entry for 'path'"
- );
-
- // no version in the package.json
- let pkg_json = build_package_json(json!({
- "name": "pkg",
- "bin": {
- "bin1": "./value1",
- "bin2": "./value2",
- }
- }));
- assert_eq!(
- resolve_bin_entry_value(&pkg_json, None)
- .err()
- .unwrap()
- .to_string(),
- concat!(
- "'/package.json' did not have a bin entry for 'pkg'\n",
- "\n",
- "Possibilities:\n",
- " * npm:pkg/bin1\n",
- " * npm:pkg/bin2",
- )
- );
-
- // no name or version in the package.json
- let pkg_json = build_package_json(json!({
- "bin": {
- "bin1": "./value1",
- "bin2": "./value2",
- }
- }));
- assert_eq!(
- resolve_bin_entry_value(&pkg_json, None)
- .err()
- .unwrap()
- .to_string(),
- concat!(
- "'/package.json' did not have a bin entry\n",
- "\n",
- "Possibilities:\n",
- " * bin1\n",
- " * bin2",
- )
- );
- }
-
- #[test]
- fn test_parse_package_name() {
- let dummy_referrer = Url::parse("http://example.com").unwrap();
-
- assert_eq!(
- parse_npm_pkg_name("fetch-blob", &dummy_referrer).unwrap(),
- ("fetch-blob".to_string(), ".".to_string(), false)
- );
- assert_eq!(
- parse_npm_pkg_name("@vue/plugin-vue", &dummy_referrer).unwrap(),
- ("@vue/plugin-vue".to_string(), ".".to_string(), true)
- );
- assert_eq!(
- parse_npm_pkg_name("@astrojs/prism/dist/highlighter", &dummy_referrer)
- .unwrap(),
- (
- "@astrojs/prism".to_string(),
- "./dist/highlighter".to_string(),
- true
- )
- );
- }
-
- #[test]
- fn test_with_known_extension() {
- let cases = &[
- ("test", "d.ts", "test.d.ts"),
- ("test.d.ts", "ts", "test.ts"),
- ("test.worker", "d.ts", "test.worker.d.ts"),
- ("test.d.mts", "js", "test.js"),
- ];
- for (path, ext, expected) in cases {
- let actual = with_known_extension(&PathBuf::from(path), ext);
- assert_eq!(actual.to_string_lossy(), *expected);
- }
- }
-
- #[test]
- fn test_types_package_name() {
- assert_eq!(types_package_name("name"), "@types/name");
- assert_eq!(
- types_package_name("@scoped/package"),
- "@types/@scoped__package"
- );
- }
-}