From 46817a0e3dc98197cd3e7af0b33efa3533f44ea0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Fri, 10 Feb 2023 16:26:39 +0100 Subject: [PATCH] refactor: clean up "cli/node/mod.rs" and "ext/node" (#17713) This commit moves some code around from "cli/node/mod.rs" to "ext/node". Additionally "ext/node" was changed to factor out "ops.rs" and "polyfill.rs" modules. --- cli/node/mod.rs | 90 +--- cli/tools/repl/session.rs | 4 +- cli/worker.rs | 11 +- ext/node/lib.rs | 869 ++++---------------------------------- ext/node/ops.rs | 573 +++++++++++++++++++++++++ ext/node/polyfill.rs | 215 ++++++++++ 6 files changed, 895 insertions(+), 867 deletions(-) create mode 100644 ext/node/ops.rs create mode 100644 ext/node/polyfill.rs diff --git a/cli/node/mod.rs b/cli/node/mod.rs index dffcb74372..c5d3de9681 100644 --- a/cli/node/mod.rs +++ b/cli/node/mod.rs @@ -13,11 +13,11 @@ use deno_core::anyhow::bail; use deno_core::anyhow::Context; use deno_core::error::generic_error; use deno_core::error::AnyError; -use deno_core::located_script_name; use deno_core::serde_json::Value; use deno_core::url::Url; -use deno_core::JsRuntime; +use deno_runtime::deno_node; use deno_runtime::deno_node::errors; +use deno_runtime::deno_node::find_builtin_node_module; use deno_runtime::deno_node::get_closest_package_json; use deno_runtime::deno_node::legacy_main_resolve; use deno_runtime::deno_node::package_exports_resolve; @@ -25,7 +25,6 @@ use deno_runtime::deno_node::package_imports_resolve; use deno_runtime::deno_node::package_resolve; use deno_runtime::deno_node::path_to_declaration_path; use deno_runtime::deno_node::NodeModuleKind; -use deno_runtime::deno_node::NodeModulePolyfill; use deno_runtime::deno_node::NodeModulePolyfillSpecifier; use deno_runtime::deno_node::NodePermissions; use deno_runtime::deno_node::NodeResolutionMode; @@ -33,8 +32,6 @@ use deno_runtime::deno_node::PackageJson; use deno_runtime::deno_node::PathClean; use deno_runtime::deno_node::RequireNpmResolver; use deno_runtime::deno_node::DEFAULT_CONDITIONS; -use deno_runtime::deno_node::NODE_GLOBAL_THIS_NAME; -use deno_runtime::deno_node::SUPPORTED_BUILTIN_NODE_MODULES; use deno_runtime::permissions::PermissionsContainer; use once_cell::sync::Lazy; use regex::Regex; @@ -123,16 +120,6 @@ static NODE_COMPAT_URL: Lazy = Lazy::new(|| { pub static MODULE_ALL_URL: Lazy = Lazy::new(|| NODE_COMPAT_URL.join("node/module_all.ts").unwrap()); -fn find_builtin_node_module(specifier: &str) -> Option<&NodeModulePolyfill> { - SUPPORTED_BUILTIN_NODE_MODULES - .iter() - .find(|m| m.name == specifier) -} - -fn is_builtin_node_module(specifier: &str) -> bool { - find_builtin_node_module(specifier).is_some() -} - pub fn resolve_builtin_node_module(specifier: &str) -> Result { if let Some(module) = find_builtin_node_module(specifier) { match module.specifier { @@ -203,49 +190,6 @@ static RESERVED_WORDS: Lazy> = Lazy::new(|| { ]) }); -pub async fn initialize_runtime( - js_runtime: &mut JsRuntime, - uses_local_node_modules_dir: bool, -) -> Result<(), AnyError> { - let source_code = &format!( - r#"(async function loadBuiltinNodeModules(moduleAllUrl, nodeGlobalThisName, usesLocalNodeModulesDir) {{ - const moduleAll = await import(moduleAllUrl); - Deno[Deno.internal].node.initialize(moduleAll.default, nodeGlobalThisName); - if (usesLocalNodeModulesDir) {{ - Deno[Deno.internal].require.setUsesLocalNodeModulesDir(); - }} - }})('{}', '{}', {});"#, - MODULE_ALL_URL.as_str(), - NODE_GLOBAL_THIS_NAME.as_str(), - uses_local_node_modules_dir, - ); - - let value = - js_runtime.execute_script(&located_script_name!(), source_code)?; - js_runtime.resolve_value(value).await?; - Ok(()) -} - -pub async fn initialize_binary_command( - js_runtime: &mut JsRuntime, - binary_name: &str, -) -> Result<(), AnyError> { - // overwrite what's done in deno_std in order to set the binary arg name - let source_code = &format!( - r#"(async function initializeBinaryCommand(binaryName) {{ - const process = Deno[Deno.internal].node.globalThis.process; - Object.defineProperty(process.argv, "0", {{ - get: () => binaryName, - }}); - }})('{binary_name}');"#, - ); - - let value = - js_runtime.execute_script(&located_script_name!(), source_code)?; - js_runtime.resolve_value(value).await?; - Ok(()) -} - /// This function is an implementation of `defaultResolve` in /// `lib/internal/modules/esm/resolve.js` from Node. pub fn node_resolve( @@ -258,7 +202,7 @@ pub fn node_resolve( // 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 is_builtin_node_module(specifier) { + if deno_node::is_builtin_node_module(specifier) { return Ok(Some(NodeResolution::BuiltIn(specifier.to_string()))); } @@ -273,7 +217,7 @@ pub fn node_resolve( let split_specifier = url.as_str().split(':'); let specifier = split_specifier.skip(1).collect::(); - if is_builtin_node_module(&specifier) { + if deno_node::is_builtin_node_module(&specifier) { return Ok(Some(NodeResolution::BuiltIn(specifier))); } } @@ -451,32 +395,6 @@ fn resolve_bin_entry_value<'a>( } } -pub fn load_cjs_module_from_ext_node( - js_runtime: &mut JsRuntime, - module: &str, - main: bool, - inspect_brk: bool, -) -> Result<(), AnyError> { - fn escape_for_single_quote_string(text: &str) -> String { - text.replace('\\', r"\\").replace('\'', r"\'") - } - - let source_code = &format!( - r#"(function loadCjsModule(module, inspectBrk) {{ - if (inspectBrk) {{ - Deno[Deno.internal].require.setInspectBrk(); - }} - Deno[Deno.internal].require.Module._load(module, null, {main}); - }})('{module}', {inspect_brk});"#, - main = main, - module = escape_for_single_quote_string(module), - inspect_brk = inspect_brk, - ); - - js_runtime.execute_script(&located_script_name!(), source_code)?; - Ok(()) -} - fn package_config_resolve( package_subpath: &str, package_dir: &Path, diff --git a/cli/tools/repl/session.rs b/cli/tools/repl/session.rs index 0e40a1e321..18b40a1b9e 100644 --- a/cli/tools/repl/session.rs +++ b/cli/tools/repl/session.rs @@ -19,6 +19,7 @@ use deno_core::serde_json; use deno_core::serde_json::Value; use deno_core::LocalInspectorSession; use deno_graph::source::Resolver; +use deno_runtime::deno_node; use deno_runtime::worker::MainWorker; use super::cdp; @@ -460,8 +461,9 @@ impl ReplSession { if !npm_imports.is_empty() || has_node_specifier { if !self.has_initialized_node_runtime { self.proc_state.prepare_node_std_graph().await?; - crate::node::initialize_runtime( + deno_node::initialize_runtime( &mut self.worker.js_runtime, + crate::node::MODULE_ALL_URL.as_str(), self.proc_state.options.node_modules_dir(), ) .await?; diff --git a/cli/worker.rs b/cli/worker.rs index 60663ebc03..3748c007b4 100644 --- a/cli/worker.rs +++ b/cli/worker.rs @@ -15,6 +15,7 @@ use deno_core::v8; use deno_core::Extension; use deno_core::ModuleId; use deno_runtime::colors; +use deno_runtime::deno_node; use deno_runtime::fmt_errors::format_js_error; use deno_runtime::ops::worker_host::CreateWebWorkerCb; use deno_runtime::ops::worker_host::WorkerEventCb; @@ -68,7 +69,7 @@ impl CliMainWorker { if self.is_main_cjs { self.ps.prepare_node_std_graph().await?; self.initialize_main_module_for_node().await?; - node::load_cjs_module_from_ext_node( + deno_node::load_cjs_module( &mut self.worker.js_runtime, &self.main_module.to_file_path().unwrap().to_string_lossy(), true, @@ -304,8 +305,9 @@ impl CliMainWorker { async fn initialize_main_module_for_node(&mut self) -> Result<(), AnyError> { self.ps.prepare_node_std_graph().await?; - node::initialize_runtime( + deno_node::initialize_runtime( &mut self.worker.js_runtime, + node::MODULE_ALL_URL.as_str(), self.ps.options.node_modules_dir(), ) .await?; @@ -317,7 +319,7 @@ impl CliMainWorker { .sub_path .as_deref() .unwrap_or(pkg_ref.req.name.as_str()); - node::initialize_binary_command( + deno_node::initialize_binary_command( &mut self.worker.js_runtime, binary_name, ) @@ -626,8 +628,9 @@ fn create_web_worker_pre_execute_module_callback( let fut = async move { // this will be up to date after pre-load if ps.npm_resolver.has_packages() { - node::initialize_runtime( + deno_node::initialize_runtime( &mut worker.js_runtime, + node::MODULE_ALL_URL.as_str(), ps.options.node_modules_dir(), ) .await?; diff --git a/ext/node/lib.rs b/ext/node/lib.rs index fc506b3b33..aabbbfafd3 100644 --- a/ext/node/lib.rs +++ b/ext/node/lib.rs @@ -1,14 +1,10 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. -use deno_core::error::generic_error; use deno_core::error::AnyError; use deno_core::include_js_files; -use deno_core::normalize_path; -use deno_core::op; -use deno_core::url::Url; +use deno_core::located_script_name; use deno_core::Extension; -use deno_core::JsRuntimeInspector; -use deno_core::OpState; +use deno_core::JsRuntime; use once_cell::sync::Lazy; use std::collections::HashSet; use std::path::Path; @@ -16,12 +12,19 @@ use std::path::PathBuf; use std::rc::Rc; pub mod errors; +mod ops; mod package_json; mod path; +mod polyfill; mod resolution; pub use package_json::PackageJson; pub use path::PathClean; +pub use polyfill::find_builtin_node_module; +pub use polyfill::is_builtin_node_module; +pub use polyfill::NodeModulePolyfill; +pub use polyfill::NodeModulePolyfillSpecifier; +pub use polyfill::SUPPORTED_BUILTIN_NODE_MODULES; pub use resolution::get_closest_package_json; pub use resolution::get_package_scope_config; pub use resolution::legacy_main_resolve; @@ -32,7 +35,6 @@ pub use resolution::path_to_declaration_path; pub use resolution::NodeModuleKind; pub use resolution::NodeResolutionMode; pub use resolution::DEFAULT_CONDITIONS; -use std::cell::RefCell; pub trait NodePermissions { fn check_read(&mut self, path: &Path) -> Result<(), AnyError>; @@ -91,28 +93,28 @@ pub fn init( "module_es_shim.js", )) .ops(vec![ - op_require_init_paths::decl(), - op_require_node_module_paths::decl::

(), - op_require_proxy_path::decl(), - op_require_is_deno_dir_package::decl(), - op_require_resolve_deno_dir::decl(), - op_require_is_request_relative::decl(), - op_require_resolve_lookup_paths::decl(), - op_require_try_self_parent_path::decl::

(), - op_require_try_self::decl::

(), - op_require_real_path::decl::

(), - op_require_path_is_absolute::decl(), - op_require_path_dirname::decl(), - op_require_stat::decl::

(), - op_require_path_resolve::decl(), - op_require_path_basename::decl(), - op_require_read_file::decl::

(), - op_require_as_file_path::decl(), - op_require_resolve_exports::decl::

(), - op_require_read_closest_package_json::decl::

(), - op_require_read_package_scope::decl::

(), - op_require_package_imports_resolve::decl::

(), - op_require_break_on_next_statement::decl(), + ops::op_require_init_paths::decl(), + ops::op_require_node_module_paths::decl::

(), + ops::op_require_proxy_path::decl(), + ops::op_require_is_deno_dir_package::decl(), + ops::op_require_resolve_deno_dir::decl(), + ops::op_require_is_request_relative::decl(), + ops::op_require_resolve_lookup_paths::decl(), + ops::op_require_try_self_parent_path::decl::

(), + ops::op_require_try_self::decl::

(), + ops::op_require_real_path::decl::

(), + ops::op_require_path_is_absolute::decl(), + ops::op_require_path_dirname::decl(), + ops::op_require_stat::decl::

(), + ops::op_require_path_resolve::decl(), + ops::op_require_path_basename::decl(), + ops::op_require_read_file::decl::

(), + ops::op_require_as_file_path::decl(), + ops::op_require_resolve_exports::decl::

(), + ops::op_require_read_closest_package_json::decl::

(), + ops::op_require_read_package_scope::decl::

(), + ops::op_require_package_imports_resolve::decl::

(), + ops::op_require_break_on_next_statement::decl(), ]) .state(move |state| { if let Some(npm_resolver) = maybe_npm_resolver.clone() { @@ -123,757 +125,72 @@ pub fn init( .build() } -fn ensure_read_permission

( - state: &mut OpState, - file_path: &Path, -) -> Result<(), AnyError> -where - P: NodePermissions + 'static, -{ - let resolver = { - let resolver = state.borrow::>(); - resolver.clone() - }; - let permissions = state.borrow_mut::

(); - resolver.ensure_read_permission(permissions, file_path) -} - -#[op] -pub fn op_require_init_paths() -> Vec { - // todo(dsherret): this code is node compat mode specific and - // we probably don't want it for small mammal, so ignore it for now - - // let (home_dir, node_path) = if cfg!(windows) { - // ( - // std::env::var("USERPROFILE").unwrap_or_else(|_| "".into()), - // std::env::var("NODE_PATH").unwrap_or_else(|_| "".into()), - // ) - // } else { - // ( - // std::env::var("HOME").unwrap_or_else(|_| "".into()), - // std::env::var("NODE_PATH").unwrap_or_else(|_| "".into()), - // ) - // }; - - // let mut prefix_dir = std::env::current_exe().unwrap(); - // if cfg!(windows) { - // prefix_dir = prefix_dir.join("..").join("..") - // } else { - // prefix_dir = prefix_dir.join("..") - // } - - // let mut paths = vec![prefix_dir.join("lib").join("node")]; - - // if !home_dir.is_empty() { - // paths.insert(0, PathBuf::from(&home_dir).join(".node_libraries")); - // paths.insert(0, PathBuf::from(&home_dir).join(".nod_modules")); - // } - - // let mut paths = paths - // .into_iter() - // .map(|p| p.to_string_lossy().to_string()) - // .collect(); - - // if !node_path.is_empty() { - // let delimiter = if cfg!(windows) { ";" } else { ":" }; - // let mut node_paths: Vec = node_path - // .split(delimiter) - // .filter(|e| !e.is_empty()) - // .map(|s| s.to_string()) - // .collect(); - // node_paths.append(&mut paths); - // paths = node_paths; - // } - - vec![] -} - -#[op] -pub fn op_require_node_module_paths

( - state: &mut OpState, - from: String, -) -> Result, AnyError> -where - P: NodePermissions + 'static, -{ - // Guarantee that "from" is absolute. - let from = deno_core::resolve_path(&from) - .unwrap() - .to_file_path() - .unwrap(); - - ensure_read_permission::

(state, &from)?; - - if cfg!(windows) { - // return root node_modules when path is 'D:\\'. - let from_str = from.to_str().unwrap(); - if from_str.len() >= 3 { - let bytes = from_str.as_bytes(); - if bytes[from_str.len() - 1] == b'\\' && bytes[from_str.len() - 2] == b':' - { - let p = from_str.to_owned() + "node_modules"; - return Ok(vec![p]); - } - } - } else { - // Return early not only to avoid unnecessary work, but to *avoid* returning - // an array of two items for a root: [ '//node_modules', '/node_modules' ] - if from.to_string_lossy() == "/" { - return Ok(vec!["/node_modules".to_string()]); - } - } - - let mut paths = vec![]; - let mut current_path = from.as_path(); - let mut maybe_parent = Some(current_path); - while let Some(parent) = maybe_parent { - if !parent.ends_with("/node_modules") { - paths.push(parent.join("node_modules").to_string_lossy().to_string()); - current_path = parent; - maybe_parent = current_path.parent(); - } - } - - if !cfg!(windows) { - // Append /node_modules to handle root paths. - paths.push("/node_modules".to_string()); - } - - Ok(paths) -} - -#[op] -fn op_require_proxy_path(filename: String) -> String { - // Allow a directory to be passed as the filename - let trailing_slash = if cfg!(windows) { - // Node also counts a trailing forward slash as a - // directory for node on Windows, but not backslashes - // on non-Windows platforms - filename.ends_with('\\') || filename.ends_with('/') - } else { - filename.ends_with('/') - }; - - if trailing_slash { - let p = PathBuf::from(filename); - p.join("noop.js").to_string_lossy().to_string() - } else { - filename - } -} - -#[op] -fn op_require_is_request_relative(request: String) -> bool { - if request.starts_with("./") || request.starts_with("../") || request == ".." - { - return true; - } - - if cfg!(windows) { - if request.starts_with(".\\") { - return true; - } - - if request.starts_with("..\\") { - return true; - } - } - - false -} - -#[op] -fn op_require_resolve_deno_dir( - state: &mut OpState, - request: String, - parent_filename: String, -) -> Option { - let resolver = state.borrow::>(); - resolver - .resolve_package_folder_from_package( - &request, - &PathBuf::from(parent_filename), - NodeResolutionMode::Execution, - ) - .ok() - .map(|p| p.to_string_lossy().to_string()) -} - -#[op] -fn op_require_is_deno_dir_package(state: &mut OpState, path: String) -> bool { - let resolver = state.borrow::>(); - resolver.in_npm_package(&PathBuf::from(path)) -} - -#[op] -fn op_require_resolve_lookup_paths( - request: String, - maybe_parent_paths: Option>, - parent_filename: String, -) -> Option> { - if !request.starts_with('.') - || (request.len() > 1 - && !request.starts_with("..") - && !request.starts_with("./") - && (!cfg!(windows) || !request.starts_with(".\\"))) - { - let module_paths = vec![]; - let mut paths = module_paths; - if let Some(mut parent_paths) = maybe_parent_paths { - if !parent_paths.is_empty() { - paths.append(&mut parent_paths); - } - } - - if !paths.is_empty() { - return Some(paths); - } else { - return None; - } - } - - // In REPL, parent.filename is null. - // if (!parent || !parent.id || !parent.filename) { - // // Make require('./path/to/foo') work - normally the path is taken - // // from realpath(__filename) but in REPL there is no filename - // const mainPaths = ['.']; - - // debug('looking for %j in %j', request, mainPaths); - // return mainPaths; - // } - - let p = PathBuf::from(parent_filename); - Some(vec![p.parent().unwrap().to_string_lossy().to_string()]) -} - -#[op] -fn op_require_path_is_absolute(p: String) -> bool { - PathBuf::from(p).is_absolute() -} - -#[op] -fn op_require_stat

( - state: &mut OpState, - path: String, -) -> Result -where - P: NodePermissions + 'static, -{ - let path = PathBuf::from(path); - ensure_read_permission::

(state, &path)?; - if let Ok(metadata) = std::fs::metadata(&path) { - if metadata.is_file() { - return Ok(0); - } else { - return Ok(1); - } - } - - Ok(-1) -} - -#[op] -fn op_require_real_path

( - state: &mut OpState, - request: String, -) -> Result -where - P: NodePermissions + 'static, -{ - let path = PathBuf::from(request); - ensure_read_permission::

(state, &path)?; - let mut canonicalized_path = path.canonicalize()?; - if cfg!(windows) { - canonicalized_path = PathBuf::from( - canonicalized_path - .display() - .to_string() - .trim_start_matches("\\\\?\\"), - ); - } - Ok(canonicalized_path.to_string_lossy().to_string()) -} - -fn path_resolve(parts: Vec) -> String { - assert!(!parts.is_empty()); - let mut p = PathBuf::from(&parts[0]); - if parts.len() > 1 { - for part in &parts[1..] { - p = p.join(part); - } - } - normalize_path(p).to_string_lossy().to_string() -} - -#[op] -fn op_require_path_resolve(parts: Vec) -> String { - path_resolve(parts) -} - -#[op] -fn op_require_path_dirname(request: String) -> Result { - let p = PathBuf::from(request); - if let Some(parent) = p.parent() { - Ok(parent.to_string_lossy().to_string()) - } else { - Err(generic_error("Path doesn't have a parent")) - } -} - -#[op] -fn op_require_path_basename(request: String) -> Result { - let p = PathBuf::from(request); - if let Some(path) = p.file_name() { - Ok(path.to_string_lossy().to_string()) - } else { - Err(generic_error("Path doesn't have a file name")) - } -} - -#[op] -fn op_require_try_self_parent_path

( - state: &mut OpState, - has_parent: bool, - maybe_parent_filename: Option, - maybe_parent_id: Option, -) -> Result, AnyError> -where - P: NodePermissions + 'static, -{ - if !has_parent { - return Ok(None); - } - - if let Some(parent_filename) = maybe_parent_filename { - return Ok(Some(parent_filename)); - } - - if let Some(parent_id) = maybe_parent_id { - if parent_id == "" || parent_id == "internal/preload" { - if let Ok(cwd) = std::env::current_dir() { - ensure_read_permission::

(state, &cwd)?; - return Ok(Some(cwd.to_string_lossy().to_string())); - } - } - } - Ok(None) -} - -#[op] -fn op_require_try_self

( - state: &mut OpState, - parent_path: Option, - request: String, -) -> Result, AnyError> -where - P: NodePermissions + 'static, -{ - if parent_path.is_none() { - return Ok(None); - } - - let resolver = state.borrow::>().clone(); - let permissions = state.borrow_mut::

(); - let pkg = resolution::get_package_scope_config( - &Url::from_file_path(parent_path.unwrap()).unwrap(), - &*resolver, - permissions, - ) - .ok(); - if pkg.is_none() { - return Ok(None); - } - - let pkg = pkg.unwrap(); - if pkg.exports.is_none() { - return Ok(None); - } - if pkg.name.is_none() { - return Ok(None); - } - - let pkg_name = pkg.name.as_ref().unwrap().to_string(); - let mut expansion = ".".to_string(); - - if request == pkg_name { - // pass - } else if request.starts_with(&format!("{pkg_name}/")) { - expansion += &request[pkg_name.len()..]; - } else { - return Ok(None); - } - - let referrer = deno_core::url::Url::from_file_path(&pkg.path).unwrap(); - if let Some(exports) = &pkg.exports { - resolution::package_exports_resolve( - &pkg.path, - expansion, - exports, - &referrer, - NodeModuleKind::Cjs, - resolution::REQUIRE_CONDITIONS, - NodeResolutionMode::Execution, - &*resolver, - permissions, - ) - .map(|r| Some(r.to_string_lossy().to_string())) - } else { - Ok(None) - } -} - -#[op] -fn op_require_read_file

( - state: &mut OpState, - file_path: String, -) -> Result -where - P: NodePermissions + 'static, -{ - let file_path = PathBuf::from(file_path); - ensure_read_permission::

(state, &file_path)?; - Ok(std::fs::read_to_string(file_path)?) -} - -#[op] -pub fn op_require_as_file_path(file_or_url: String) -> String { - if let Ok(url) = Url::parse(&file_or_url) { - if let Ok(p) = url.to_file_path() { - return p.to_string_lossy().to_string(); - } - } - - file_or_url -} - -#[op] -fn op_require_resolve_exports

( - state: &mut OpState, +pub async fn initialize_runtime( + js_runtime: &mut JsRuntime, + module_all_url: &str, uses_local_node_modules_dir: bool, - modules_path: String, - _request: String, - name: String, - expansion: String, - parent_path: String, -) -> Result, AnyError> -where - P: NodePermissions + 'static, -{ - let resolver = state.borrow::>().clone(); - let permissions = state.borrow_mut::

(); +) -> Result<(), AnyError> { + let source_code = &format!( + r#"(async function loadBuiltinNodeModules(moduleAllUrl, nodeGlobalThisName, usesLocalNodeModulesDir) {{ + const moduleAll = await import(moduleAllUrl); + Deno[Deno.internal].node.initialize(moduleAll.default, nodeGlobalThisName); + if (usesLocalNodeModulesDir) {{ + Deno[Deno.internal].require.setUsesLocalNodeModulesDir(); + }} + }})('{}', '{}', {});"#, + module_all_url, + NODE_GLOBAL_THIS_NAME.as_str(), + uses_local_node_modules_dir, + ); - let pkg_path = if resolver.in_npm_package(&PathBuf::from(&modules_path)) - && !uses_local_node_modules_dir - { - modules_path - } else { - path_resolve(vec![modules_path, name]) - }; - let pkg = PackageJson::load( - &*resolver, - permissions, - PathBuf::from(&pkg_path).join("package.json"), - )?; + let value = + js_runtime.execute_script(&located_script_name!(), source_code)?; + js_runtime.resolve_value(value).await?; + Ok(()) +} - if let Some(exports) = &pkg.exports { - let referrer = Url::from_file_path(parent_path).unwrap(); - resolution::package_exports_resolve( - &pkg.path, - format!(".{expansion}"), - exports, - &referrer, - NodeModuleKind::Cjs, - resolution::REQUIRE_CONDITIONS, - NodeResolutionMode::Execution, - &*resolver, - permissions, - ) - .map(|r| Some(r.to_string_lossy().to_string())) - } else { - Ok(None) +pub fn load_cjs_module( + js_runtime: &mut JsRuntime, + module: &str, + main: bool, + inspect_brk: bool, +) -> Result<(), AnyError> { + fn escape_for_single_quote_string(text: &str) -> String { + text.replace('\\', r"\\").replace('\'', r"\'") } + + let source_code = &format!( + r#"(function loadCjsModule(module, inspectBrk) {{ + if (inspectBrk) {{ + Deno[Deno.internal].require.setInspectBrk(); + }} + Deno[Deno.internal].require.Module._load(module, null, {main}); + }})('{module}', {inspect_brk});"#, + main = main, + module = escape_for_single_quote_string(module), + inspect_brk = inspect_brk, + ); + + js_runtime.execute_script(&located_script_name!(), source_code)?; + Ok(()) } -#[op] -fn op_require_read_closest_package_json

( - state: &mut OpState, - filename: String, -) -> Result -where - P: NodePermissions + 'static, -{ - ensure_read_permission::

( - state, - PathBuf::from(&filename).parent().unwrap(), - )?; - let resolver = state.borrow::>().clone(); - let permissions = state.borrow_mut::

(); - resolution::get_closest_package_json( - &Url::from_file_path(filename).unwrap(), - &*resolver, - permissions, - ) +pub async fn initialize_binary_command( + js_runtime: &mut JsRuntime, + binary_name: &str, +) -> Result<(), AnyError> { + // overwrite what's done in deno_std in order to set the binary arg name + let source_code = &format!( + r#"(async function initializeBinaryCommand(binaryName) {{ + const process = Deno[Deno.internal].node.globalThis.process; + Object.defineProperty(process.argv, "0", {{ + get: () => binaryName, + }}); + }})('{binary_name}');"#, + ); + + let value = + js_runtime.execute_script(&located_script_name!(), source_code)?; + js_runtime.resolve_value(value).await?; + Ok(()) } - -#[op] -fn op_require_read_package_scope

( - state: &mut OpState, - package_json_path: String, -) -> Option -where - P: NodePermissions + 'static, -{ - let resolver = state.borrow::>().clone(); - let permissions = state.borrow_mut::

(); - let package_json_path = PathBuf::from(package_json_path); - PackageJson::load(&*resolver, permissions, package_json_path).ok() -} - -#[op] -fn op_require_package_imports_resolve

( - state: &mut OpState, - parent_filename: String, - request: String, -) -> Result, AnyError> -where - P: NodePermissions + 'static, -{ - let parent_path = PathBuf::from(&parent_filename); - ensure_read_permission::

(state, &parent_path)?; - let resolver = state.borrow::>().clone(); - let permissions = state.borrow_mut::

(); - let pkg = PackageJson::load( - &*resolver, - permissions, - parent_path.join("package.json"), - )?; - - if pkg.imports.is_some() { - let referrer = - deno_core::url::Url::from_file_path(&parent_filename).unwrap(); - let r = resolution::package_imports_resolve( - &request, - &referrer, - NodeModuleKind::Cjs, - resolution::REQUIRE_CONDITIONS, - NodeResolutionMode::Execution, - &*resolver, - permissions, - ) - .map(|r| Some(Url::from_file_path(r).unwrap().to_string())); - state.put(resolver); - r - } else { - Ok(None) - } -} - -#[op] -fn op_require_break_on_next_statement(state: &mut OpState) { - let inspector = state.borrow::>>(); - inspector - .borrow_mut() - .wait_for_session_and_break_on_next_statement() -} - -pub enum NodeModulePolyfillSpecifier { - /// An internal module specifier, like "internal:deno_node/assert.ts". The - /// module must be either embedded in the binary or snapshotted. - Embedded(&'static str), - - /// Specifier relative to the root of `deno_std` repo, like "node/assert.ts" - StdNode(&'static str), -} - -pub struct NodeModulePolyfill { - /// Name of the module like "assert" or "timers/promises" - pub name: &'static str, - pub specifier: NodeModulePolyfillSpecifier, -} - -pub static SUPPORTED_BUILTIN_NODE_MODULES: &[NodeModulePolyfill] = &[ - NodeModulePolyfill { - name: "assert", - specifier: NodeModulePolyfillSpecifier::StdNode("node/assert.ts"), - }, - NodeModulePolyfill { - name: "assert/strict", - specifier: NodeModulePolyfillSpecifier::StdNode("node/assert/strict.ts"), - }, - NodeModulePolyfill { - name: "async_hooks", - specifier: NodeModulePolyfillSpecifier::StdNode("node/async_hooks.ts"), - }, - NodeModulePolyfill { - name: "buffer", - specifier: NodeModulePolyfillSpecifier::StdNode("node/buffer.ts"), - }, - NodeModulePolyfill { - name: "child_process", - specifier: NodeModulePolyfillSpecifier::StdNode("node/child_process.ts"), - }, - NodeModulePolyfill { - name: "cluster", - specifier: NodeModulePolyfillSpecifier::StdNode("node/cluster.ts"), - }, - NodeModulePolyfill { - name: "console", - specifier: NodeModulePolyfillSpecifier::StdNode("node/console.ts"), - }, - NodeModulePolyfill { - name: "constants", - specifier: NodeModulePolyfillSpecifier::StdNode("node/constants.ts"), - }, - NodeModulePolyfill { - name: "crypto", - specifier: NodeModulePolyfillSpecifier::StdNode("node/crypto.ts"), - }, - NodeModulePolyfill { - name: "dgram", - specifier: NodeModulePolyfillSpecifier::StdNode("node/dgram.ts"), - }, - NodeModulePolyfill { - name: "dns", - specifier: NodeModulePolyfillSpecifier::StdNode("node/dns.ts"), - }, - NodeModulePolyfill { - name: "dns/promises", - specifier: NodeModulePolyfillSpecifier::StdNode("node/dns/promises.ts"), - }, - NodeModulePolyfill { - name: "domain", - specifier: NodeModulePolyfillSpecifier::StdNode("node/domain.ts"), - }, - NodeModulePolyfill { - name: "events", - specifier: NodeModulePolyfillSpecifier::StdNode("node/events.ts"), - }, - NodeModulePolyfill { - name: "fs", - specifier: NodeModulePolyfillSpecifier::StdNode("node/fs.ts"), - }, - NodeModulePolyfill { - name: "fs/promises", - specifier: NodeModulePolyfillSpecifier::StdNode("node/fs/promises.ts"), - }, - NodeModulePolyfill { - name: "http", - specifier: NodeModulePolyfillSpecifier::StdNode("node/http.ts"), - }, - NodeModulePolyfill { - name: "https", - specifier: NodeModulePolyfillSpecifier::StdNode("node/https.ts"), - }, - NodeModulePolyfill { - name: "module", - specifier: NodeModulePolyfillSpecifier::Embedded( - "internal:deno_node/module_es_shim.js", - ), - }, - NodeModulePolyfill { - name: "net", - specifier: NodeModulePolyfillSpecifier::StdNode("node/net.ts"), - }, - NodeModulePolyfill { - name: "os", - specifier: NodeModulePolyfillSpecifier::StdNode("node/os.ts"), - }, - NodeModulePolyfill { - name: "path", - specifier: NodeModulePolyfillSpecifier::StdNode("node/path.ts"), - }, - NodeModulePolyfill { - name: "path/posix", - specifier: NodeModulePolyfillSpecifier::StdNode("node/path/posix.ts"), - }, - NodeModulePolyfill { - name: "path/win32", - specifier: NodeModulePolyfillSpecifier::StdNode("node/path/win32.ts"), - }, - NodeModulePolyfill { - name: "perf_hooks", - specifier: NodeModulePolyfillSpecifier::StdNode("node/perf_hooks.ts"), - }, - NodeModulePolyfill { - name: "process", - specifier: NodeModulePolyfillSpecifier::StdNode("node/process.ts"), - }, - NodeModulePolyfill { - name: "querystring", - specifier: NodeModulePolyfillSpecifier::StdNode("node/querystring.ts"), - }, - NodeModulePolyfill { - name: "readline", - specifier: NodeModulePolyfillSpecifier::StdNode("node/readline.ts"), - }, - NodeModulePolyfill { - name: "stream", - specifier: NodeModulePolyfillSpecifier::StdNode("node/stream.ts"), - }, - NodeModulePolyfill { - name: "stream/consumers", - specifier: NodeModulePolyfillSpecifier::StdNode( - "node/stream/consumers.mjs", - ), - }, - NodeModulePolyfill { - name: "stream/promises", - specifier: NodeModulePolyfillSpecifier::StdNode("node/stream/promises.mjs"), - }, - NodeModulePolyfill { - name: "stream/web", - specifier: NodeModulePolyfillSpecifier::StdNode("node/stream/web.ts"), - }, - NodeModulePolyfill { - name: "string_decoder", - specifier: NodeModulePolyfillSpecifier::StdNode("node/string_decoder.ts"), - }, - NodeModulePolyfill { - name: "sys", - specifier: NodeModulePolyfillSpecifier::StdNode("node/sys.ts"), - }, - NodeModulePolyfill { - name: "timers", - specifier: NodeModulePolyfillSpecifier::StdNode("node/timers.ts"), - }, - NodeModulePolyfill { - name: "timers/promises", - specifier: NodeModulePolyfillSpecifier::StdNode("node/timers/promises.ts"), - }, - NodeModulePolyfill { - name: "tls", - specifier: NodeModulePolyfillSpecifier::StdNode("node/tls.ts"), - }, - NodeModulePolyfill { - name: "tty", - specifier: NodeModulePolyfillSpecifier::StdNode("node/tty.ts"), - }, - NodeModulePolyfill { - name: "url", - specifier: NodeModulePolyfillSpecifier::StdNode("node/url.ts"), - }, - NodeModulePolyfill { - name: "util", - specifier: NodeModulePolyfillSpecifier::StdNode("node/util.ts"), - }, - NodeModulePolyfill { - name: "util/types", - specifier: NodeModulePolyfillSpecifier::StdNode("node/util/types.ts"), - }, - NodeModulePolyfill { - name: "v8", - specifier: NodeModulePolyfillSpecifier::StdNode("node/v8.ts"), - }, - NodeModulePolyfill { - name: "vm", - specifier: NodeModulePolyfillSpecifier::StdNode("node/vm.ts"), - }, - NodeModulePolyfill { - name: "worker_threads", - specifier: NodeModulePolyfillSpecifier::StdNode("node/worker_threads.ts"), - }, - NodeModulePolyfill { - name: "zlib", - specifier: NodeModulePolyfillSpecifier::StdNode("node/zlib.ts"), - }, -]; diff --git a/ext/node/ops.rs b/ext/node/ops.rs new file mode 100644 index 0000000000..046578ca53 --- /dev/null +++ b/ext/node/ops.rs @@ -0,0 +1,573 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +use deno_core::error::generic_error; +use deno_core::error::AnyError; +use deno_core::normalize_path; +use deno_core::op; +use deno_core::url::Url; +use deno_core::JsRuntimeInspector; +use deno_core::OpState; +use std::cell::RefCell; +use std::path::Path; +use std::path::PathBuf; +use std::rc::Rc; + +use super::resolution; +use super::NodeModuleKind; +use super::NodePermissions; +use super::NodeResolutionMode; +use super::PackageJson; +use super::RequireNpmResolver; + +fn ensure_read_permission

( + state: &mut OpState, + file_path: &Path, +) -> Result<(), AnyError> +where + P: NodePermissions + 'static, +{ + let resolver = { + let resolver = state.borrow::>(); + resolver.clone() + }; + let permissions = state.borrow_mut::

(); + resolver.ensure_read_permission(permissions, file_path) +} + +#[op] +pub fn op_require_init_paths() -> Vec { + // todo(dsherret): this code is node compat mode specific and + // we probably don't want it for small mammal, so ignore it for now + + // let (home_dir, node_path) = if cfg!(windows) { + // ( + // std::env::var("USERPROFILE").unwrap_or_else(|_| "".into()), + // std::env::var("NODE_PATH").unwrap_or_else(|_| "".into()), + // ) + // } else { + // ( + // std::env::var("HOME").unwrap_or_else(|_| "".into()), + // std::env::var("NODE_PATH").unwrap_or_else(|_| "".into()), + // ) + // }; + + // let mut prefix_dir = std::env::current_exe().unwrap(); + // if cfg!(windows) { + // prefix_dir = prefix_dir.join("..").join("..") + // } else { + // prefix_dir = prefix_dir.join("..") + // } + + // let mut paths = vec![prefix_dir.join("lib").join("node")]; + + // if !home_dir.is_empty() { + // paths.insert(0, PathBuf::from(&home_dir).join(".node_libraries")); + // paths.insert(0, PathBuf::from(&home_dir).join(".nod_modules")); + // } + + // let mut paths = paths + // .into_iter() + // .map(|p| p.to_string_lossy().to_string()) + // .collect(); + + // if !node_path.is_empty() { + // let delimiter = if cfg!(windows) { ";" } else { ":" }; + // let mut node_paths: Vec = node_path + // .split(delimiter) + // .filter(|e| !e.is_empty()) + // .map(|s| s.to_string()) + // .collect(); + // node_paths.append(&mut paths); + // paths = node_paths; + // } + + vec![] +} + +#[op] +pub fn op_require_node_module_paths

( + state: &mut OpState, + from: String, +) -> Result, AnyError> +where + P: NodePermissions + 'static, +{ + // Guarantee that "from" is absolute. + let from = deno_core::resolve_path(&from) + .unwrap() + .to_file_path() + .unwrap(); + + ensure_read_permission::

(state, &from)?; + + if cfg!(windows) { + // return root node_modules when path is 'D:\\'. + let from_str = from.to_str().unwrap(); + if from_str.len() >= 3 { + let bytes = from_str.as_bytes(); + if bytes[from_str.len() - 1] == b'\\' && bytes[from_str.len() - 2] == b':' + { + let p = from_str.to_owned() + "node_modules"; + return Ok(vec![p]); + } + } + } else { + // Return early not only to avoid unnecessary work, but to *avoid* returning + // an array of two items for a root: [ '//node_modules', '/node_modules' ] + if from.to_string_lossy() == "/" { + return Ok(vec!["/node_modules".to_string()]); + } + } + + let mut paths = vec![]; + let mut current_path = from.as_path(); + let mut maybe_parent = Some(current_path); + while let Some(parent) = maybe_parent { + if !parent.ends_with("/node_modules") { + paths.push(parent.join("node_modules").to_string_lossy().to_string()); + current_path = parent; + maybe_parent = current_path.parent(); + } + } + + if !cfg!(windows) { + // Append /node_modules to handle root paths. + paths.push("/node_modules".to_string()); + } + + Ok(paths) +} + +#[op] +fn op_require_proxy_path(filename: String) -> String { + // Allow a directory to be passed as the filename + let trailing_slash = if cfg!(windows) { + // Node also counts a trailing forward slash as a + // directory for node on Windows, but not backslashes + // on non-Windows platforms + filename.ends_with('\\') || filename.ends_with('/') + } else { + filename.ends_with('/') + }; + + if trailing_slash { + let p = PathBuf::from(filename); + p.join("noop.js").to_string_lossy().to_string() + } else { + filename + } +} + +#[op] +fn op_require_is_request_relative(request: String) -> bool { + if request.starts_with("./") || request.starts_with("../") || request == ".." + { + return true; + } + + if cfg!(windows) { + if request.starts_with(".\\") { + return true; + } + + if request.starts_with("..\\") { + return true; + } + } + + false +} + +#[op] +fn op_require_resolve_deno_dir( + state: &mut OpState, + request: String, + parent_filename: String, +) -> Option { + let resolver = state.borrow::>(); + resolver + .resolve_package_folder_from_package( + &request, + &PathBuf::from(parent_filename), + NodeResolutionMode::Execution, + ) + .ok() + .map(|p| p.to_string_lossy().to_string()) +} + +#[op] +fn op_require_is_deno_dir_package(state: &mut OpState, path: String) -> bool { + let resolver = state.borrow::>(); + resolver.in_npm_package(&PathBuf::from(path)) +} + +#[op] +fn op_require_resolve_lookup_paths( + request: String, + maybe_parent_paths: Option>, + parent_filename: String, +) -> Option> { + if !request.starts_with('.') + || (request.len() > 1 + && !request.starts_with("..") + && !request.starts_with("./") + && (!cfg!(windows) || !request.starts_with(".\\"))) + { + let module_paths = vec![]; + let mut paths = module_paths; + if let Some(mut parent_paths) = maybe_parent_paths { + if !parent_paths.is_empty() { + paths.append(&mut parent_paths); + } + } + + if !paths.is_empty() { + return Some(paths); + } else { + return None; + } + } + + // In REPL, parent.filename is null. + // if (!parent || !parent.id || !parent.filename) { + // // Make require('./path/to/foo') work - normally the path is taken + // // from realpath(__filename) but in REPL there is no filename + // const mainPaths = ['.']; + + // debug('looking for %j in %j', request, mainPaths); + // return mainPaths; + // } + + let p = PathBuf::from(parent_filename); + Some(vec![p.parent().unwrap().to_string_lossy().to_string()]) +} + +#[op] +fn op_require_path_is_absolute(p: String) -> bool { + PathBuf::from(p).is_absolute() +} + +#[op] +fn op_require_stat

( + state: &mut OpState, + path: String, +) -> Result +where + P: NodePermissions + 'static, +{ + let path = PathBuf::from(path); + ensure_read_permission::

(state, &path)?; + if let Ok(metadata) = std::fs::metadata(&path) { + if metadata.is_file() { + return Ok(0); + } else { + return Ok(1); + } + } + + Ok(-1) +} + +#[op] +fn op_require_real_path

( + state: &mut OpState, + request: String, +) -> Result +where + P: NodePermissions + 'static, +{ + let path = PathBuf::from(request); + ensure_read_permission::

(state, &path)?; + let mut canonicalized_path = path.canonicalize()?; + if cfg!(windows) { + canonicalized_path = PathBuf::from( + canonicalized_path + .display() + .to_string() + .trim_start_matches("\\\\?\\"), + ); + } + Ok(canonicalized_path.to_string_lossy().to_string()) +} + +fn path_resolve(parts: Vec) -> String { + assert!(!parts.is_empty()); + let mut p = PathBuf::from(&parts[0]); + if parts.len() > 1 { + for part in &parts[1..] { + p = p.join(part); + } + } + normalize_path(p).to_string_lossy().to_string() +} + +#[op] +fn op_require_path_resolve(parts: Vec) -> String { + path_resolve(parts) +} + +#[op] +fn op_require_path_dirname(request: String) -> Result { + let p = PathBuf::from(request); + if let Some(parent) = p.parent() { + Ok(parent.to_string_lossy().to_string()) + } else { + Err(generic_error("Path doesn't have a parent")) + } +} + +#[op] +fn op_require_path_basename(request: String) -> Result { + let p = PathBuf::from(request); + if let Some(path) = p.file_name() { + Ok(path.to_string_lossy().to_string()) + } else { + Err(generic_error("Path doesn't have a file name")) + } +} + +#[op] +fn op_require_try_self_parent_path

( + state: &mut OpState, + has_parent: bool, + maybe_parent_filename: Option, + maybe_parent_id: Option, +) -> Result, AnyError> +where + P: NodePermissions + 'static, +{ + if !has_parent { + return Ok(None); + } + + if let Some(parent_filename) = maybe_parent_filename { + return Ok(Some(parent_filename)); + } + + if let Some(parent_id) = maybe_parent_id { + if parent_id == "" || parent_id == "internal/preload" { + if let Ok(cwd) = std::env::current_dir() { + ensure_read_permission::

(state, &cwd)?; + return Ok(Some(cwd.to_string_lossy().to_string())); + } + } + } + Ok(None) +} + +#[op] +fn op_require_try_self

( + state: &mut OpState, + parent_path: Option, + request: String, +) -> Result, AnyError> +where + P: NodePermissions + 'static, +{ + if parent_path.is_none() { + return Ok(None); + } + + let resolver = state.borrow::>().clone(); + let permissions = state.borrow_mut::

(); + let pkg = resolution::get_package_scope_config( + &Url::from_file_path(parent_path.unwrap()).unwrap(), + &*resolver, + permissions, + ) + .ok(); + if pkg.is_none() { + return Ok(None); + } + + let pkg = pkg.unwrap(); + if pkg.exports.is_none() { + return Ok(None); + } + if pkg.name.is_none() { + return Ok(None); + } + + let pkg_name = pkg.name.as_ref().unwrap().to_string(); + let mut expansion = ".".to_string(); + + if request == pkg_name { + // pass + } else if request.starts_with(&format!("{pkg_name}/")) { + expansion += &request[pkg_name.len()..]; + } else { + return Ok(None); + } + + let referrer = deno_core::url::Url::from_file_path(&pkg.path).unwrap(); + if let Some(exports) = &pkg.exports { + resolution::package_exports_resolve( + &pkg.path, + expansion, + exports, + &referrer, + NodeModuleKind::Cjs, + resolution::REQUIRE_CONDITIONS, + NodeResolutionMode::Execution, + &*resolver, + permissions, + ) + .map(|r| Some(r.to_string_lossy().to_string())) + } else { + Ok(None) + } +} + +#[op] +fn op_require_read_file

( + state: &mut OpState, + file_path: String, +) -> Result +where + P: NodePermissions + 'static, +{ + let file_path = PathBuf::from(file_path); + ensure_read_permission::

(state, &file_path)?; + Ok(std::fs::read_to_string(file_path)?) +} + +#[op] +pub fn op_require_as_file_path(file_or_url: String) -> String { + if let Ok(url) = Url::parse(&file_or_url) { + if let Ok(p) = url.to_file_path() { + return p.to_string_lossy().to_string(); + } + } + + file_or_url +} + +#[op] +fn op_require_resolve_exports

( + state: &mut OpState, + uses_local_node_modules_dir: bool, + modules_path: String, + _request: String, + name: String, + expansion: String, + parent_path: String, +) -> Result, AnyError> +where + P: NodePermissions + 'static, +{ + let resolver = state.borrow::>().clone(); + let permissions = state.borrow_mut::

(); + + let pkg_path = if resolver.in_npm_package(&PathBuf::from(&modules_path)) + && !uses_local_node_modules_dir + { + modules_path + } else { + path_resolve(vec![modules_path, name]) + }; + let pkg = PackageJson::load( + &*resolver, + permissions, + PathBuf::from(&pkg_path).join("package.json"), + )?; + + if let Some(exports) = &pkg.exports { + let referrer = Url::from_file_path(parent_path).unwrap(); + resolution::package_exports_resolve( + &pkg.path, + format!(".{expansion}"), + exports, + &referrer, + NodeModuleKind::Cjs, + resolution::REQUIRE_CONDITIONS, + NodeResolutionMode::Execution, + &*resolver, + permissions, + ) + .map(|r| Some(r.to_string_lossy().to_string())) + } else { + Ok(None) + } +} + +#[op] +fn op_require_read_closest_package_json

( + state: &mut OpState, + filename: String, +) -> Result +where + P: NodePermissions + 'static, +{ + ensure_read_permission::

( + state, + PathBuf::from(&filename).parent().unwrap(), + )?; + let resolver = state.borrow::>().clone(); + let permissions = state.borrow_mut::

(); + resolution::get_closest_package_json( + &Url::from_file_path(filename).unwrap(), + &*resolver, + permissions, + ) +} + +#[op] +fn op_require_read_package_scope

( + state: &mut OpState, + package_json_path: String, +) -> Option +where + P: NodePermissions + 'static, +{ + let resolver = state.borrow::>().clone(); + let permissions = state.borrow_mut::

(); + let package_json_path = PathBuf::from(package_json_path); + PackageJson::load(&*resolver, permissions, package_json_path).ok() +} + +#[op] +fn op_require_package_imports_resolve

( + state: &mut OpState, + parent_filename: String, + request: String, +) -> Result, AnyError> +where + P: NodePermissions + 'static, +{ + let parent_path = PathBuf::from(&parent_filename); + ensure_read_permission::

(state, &parent_path)?; + let resolver = state.borrow::>().clone(); + let permissions = state.borrow_mut::

(); + let pkg = PackageJson::load( + &*resolver, + permissions, + parent_path.join("package.json"), + )?; + + if pkg.imports.is_some() { + let referrer = + deno_core::url::Url::from_file_path(&parent_filename).unwrap(); + let r = resolution::package_imports_resolve( + &request, + &referrer, + NodeModuleKind::Cjs, + resolution::REQUIRE_CONDITIONS, + NodeResolutionMode::Execution, + &*resolver, + permissions, + ) + .map(|r| Some(Url::from_file_path(r).unwrap().to_string())); + state.put(resolver); + r + } else { + Ok(None) + } +} + +#[op] +fn op_require_break_on_next_statement(state: &mut OpState) { + let inspector = state.borrow::>>(); + inspector + .borrow_mut() + .wait_for_session_and_break_on_next_statement() +} diff --git a/ext/node/polyfill.rs b/ext/node/polyfill.rs new file mode 100644 index 0000000000..371b27be6c --- /dev/null +++ b/ext/node/polyfill.rs @@ -0,0 +1,215 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +pub fn find_builtin_node_module( + specifier: &str, +) -> Option<&NodeModulePolyfill> { + SUPPORTED_BUILTIN_NODE_MODULES + .iter() + .find(|m| m.name == specifier) +} + +pub fn is_builtin_node_module(specifier: &str) -> bool { + find_builtin_node_module(specifier).is_some() +} + +pub enum NodeModulePolyfillSpecifier { + /// An internal module specifier, like "internal:deno_node/assert.ts". The + /// module must be either embedded in the binary or snapshotted. + Embedded(&'static str), + + /// Specifier relative to the root of `deno_std` repo, like "node/assert.ts" + StdNode(&'static str), +} + +pub struct NodeModulePolyfill { + /// Name of the module like "assert" or "timers/promises" + pub name: &'static str, + pub specifier: NodeModulePolyfillSpecifier, +} + +pub static SUPPORTED_BUILTIN_NODE_MODULES: &[NodeModulePolyfill] = &[ + NodeModulePolyfill { + name: "assert", + specifier: NodeModulePolyfillSpecifier::StdNode("node/assert.ts"), + }, + NodeModulePolyfill { + name: "assert/strict", + specifier: NodeModulePolyfillSpecifier::StdNode("node/assert/strict.ts"), + }, + NodeModulePolyfill { + name: "async_hooks", + specifier: NodeModulePolyfillSpecifier::StdNode("node/async_hooks.ts"), + }, + NodeModulePolyfill { + name: "buffer", + specifier: NodeModulePolyfillSpecifier::StdNode("node/buffer.ts"), + }, + NodeModulePolyfill { + name: "child_process", + specifier: NodeModulePolyfillSpecifier::StdNode("node/child_process.ts"), + }, + NodeModulePolyfill { + name: "cluster", + specifier: NodeModulePolyfillSpecifier::StdNode("node/cluster.ts"), + }, + NodeModulePolyfill { + name: "console", + specifier: NodeModulePolyfillSpecifier::StdNode("node/console.ts"), + }, + NodeModulePolyfill { + name: "constants", + specifier: NodeModulePolyfillSpecifier::StdNode("node/constants.ts"), + }, + NodeModulePolyfill { + name: "crypto", + specifier: NodeModulePolyfillSpecifier::StdNode("node/crypto.ts"), + }, + NodeModulePolyfill { + name: "dgram", + specifier: NodeModulePolyfillSpecifier::StdNode("node/dgram.ts"), + }, + NodeModulePolyfill { + name: "dns", + specifier: NodeModulePolyfillSpecifier::StdNode("node/dns.ts"), + }, + NodeModulePolyfill { + name: "dns/promises", + specifier: NodeModulePolyfillSpecifier::StdNode("node/dns/promises.ts"), + }, + NodeModulePolyfill { + name: "domain", + specifier: NodeModulePolyfillSpecifier::StdNode("node/domain.ts"), + }, + NodeModulePolyfill { + name: "events", + specifier: NodeModulePolyfillSpecifier::StdNode("node/events.ts"), + }, + NodeModulePolyfill { + name: "fs", + specifier: NodeModulePolyfillSpecifier::StdNode("node/fs.ts"), + }, + NodeModulePolyfill { + name: "fs/promises", + specifier: NodeModulePolyfillSpecifier::StdNode("node/fs/promises.ts"), + }, + NodeModulePolyfill { + name: "http", + specifier: NodeModulePolyfillSpecifier::StdNode("node/http.ts"), + }, + NodeModulePolyfill { + name: "https", + specifier: NodeModulePolyfillSpecifier::StdNode("node/https.ts"), + }, + NodeModulePolyfill { + name: "module", + specifier: NodeModulePolyfillSpecifier::Embedded( + "internal:deno_node/module_es_shim.js", + ), + }, + NodeModulePolyfill { + name: "net", + specifier: NodeModulePolyfillSpecifier::StdNode("node/net.ts"), + }, + NodeModulePolyfill { + name: "os", + specifier: NodeModulePolyfillSpecifier::StdNode("node/os.ts"), + }, + NodeModulePolyfill { + name: "path", + specifier: NodeModulePolyfillSpecifier::StdNode("node/path.ts"), + }, + NodeModulePolyfill { + name: "path/posix", + specifier: NodeModulePolyfillSpecifier::StdNode("node/path/posix.ts"), + }, + NodeModulePolyfill { + name: "path/win32", + specifier: NodeModulePolyfillSpecifier::StdNode("node/path/win32.ts"), + }, + NodeModulePolyfill { + name: "perf_hooks", + specifier: NodeModulePolyfillSpecifier::StdNode("node/perf_hooks.ts"), + }, + NodeModulePolyfill { + name: "process", + specifier: NodeModulePolyfillSpecifier::StdNode("node/process.ts"), + }, + NodeModulePolyfill { + name: "querystring", + specifier: NodeModulePolyfillSpecifier::StdNode("node/querystring.ts"), + }, + NodeModulePolyfill { + name: "readline", + specifier: NodeModulePolyfillSpecifier::StdNode("node/readline.ts"), + }, + NodeModulePolyfill { + name: "stream", + specifier: NodeModulePolyfillSpecifier::StdNode("node/stream.ts"), + }, + NodeModulePolyfill { + name: "stream/consumers", + specifier: NodeModulePolyfillSpecifier::StdNode( + "node/stream/consumers.mjs", + ), + }, + NodeModulePolyfill { + name: "stream/promises", + specifier: NodeModulePolyfillSpecifier::StdNode("node/stream/promises.mjs"), + }, + NodeModulePolyfill { + name: "stream/web", + specifier: NodeModulePolyfillSpecifier::StdNode("node/stream/web.ts"), + }, + NodeModulePolyfill { + name: "string_decoder", + specifier: NodeModulePolyfillSpecifier::StdNode("node/string_decoder.ts"), + }, + NodeModulePolyfill { + name: "sys", + specifier: NodeModulePolyfillSpecifier::StdNode("node/sys.ts"), + }, + NodeModulePolyfill { + name: "timers", + specifier: NodeModulePolyfillSpecifier::StdNode("node/timers.ts"), + }, + NodeModulePolyfill { + name: "timers/promises", + specifier: NodeModulePolyfillSpecifier::StdNode("node/timers/promises.ts"), + }, + NodeModulePolyfill { + name: "tls", + specifier: NodeModulePolyfillSpecifier::StdNode("node/tls.ts"), + }, + NodeModulePolyfill { + name: "tty", + specifier: NodeModulePolyfillSpecifier::StdNode("node/tty.ts"), + }, + NodeModulePolyfill { + name: "url", + specifier: NodeModulePolyfillSpecifier::StdNode("node/url.ts"), + }, + NodeModulePolyfill { + name: "util", + specifier: NodeModulePolyfillSpecifier::StdNode("node/util.ts"), + }, + NodeModulePolyfill { + name: "util/types", + specifier: NodeModulePolyfillSpecifier::StdNode("node/util/types.ts"), + }, + NodeModulePolyfill { + name: "v8", + specifier: NodeModulePolyfillSpecifier::StdNode("node/v8.ts"), + }, + NodeModulePolyfill { + name: "vm", + specifier: NodeModulePolyfillSpecifier::StdNode("node/vm.ts"), + }, + NodeModulePolyfill { + name: "worker_threads", + specifier: NodeModulePolyfillSpecifier::StdNode("node/worker_threads.ts"), + }, + NodeModulePolyfill { + name: "zlib", + specifier: NodeModulePolyfillSpecifier::StdNode("node/zlib.ts"), + }, +];