deno_node 0.61.0

Node compatibility for Deno
Documentation
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.

use std::collections::HashSet;
use std::path::Path;
use std::path::PathBuf;
use std::rc::Rc;

use deno_core::error::AnyError;
use deno_core::located_script_name;
use deno_core::op2;
use deno_core::url::Url;
#[allow(unused_imports)]
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 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 package_json::PackageJson;
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::parse_npm_pkg_name;
pub use resolution::NodeModuleKind;
pub use resolution::NodeResolution;
pub use resolution::NodeResolutionMode;
pub use resolution::NodeResolver;

use crate::global::global_object_middleware;
use crate::global::global_template_middleware;

pub trait NodePermissions {
  fn check_net_url(
    &mut self,
    url: &Url,
    api_name: &str,
  ) -> Result<(), AnyError>;
  fn check_read(&self, path: &Path) -> Result<(), AnyError>;
  fn check_sys(&self, kind: &str, api_name: &str) -> Result<(), AnyError>;
}

pub(crate) struct AllowAllNodePermissions;

impl NodePermissions for AllowAllNodePermissions {
  fn check_net_url(
    &mut self,
    _url: &Url,
    _api_name: &str,
  ) -> Result<(), AnyError> {
    Ok(())
  }
  fn check_read(&self, _path: &Path) -> Result<(), AnyError> {
    Ok(())
  }
  fn check_sys(&self, _kind: &str, _api_name: &str) -> Result<(), AnyError> {
    Ok(())
  }
}

#[allow(clippy::disallowed_types)]
pub type NpmResolverRc = deno_fs::sync::MaybeArc<dyn NpmResolver>;

pub trait NpmResolver: 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
  /// variable when doing a `child_process.fork`. The implementor can then check this environment
  /// variable on startup to repopulate the internal npm state.
  fn get_npm_process_state(&self) -> String {
    // 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,
    mode: NodeResolutionMode,
  ) -> Result<PathBuf, AnyError>;

  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)
  }

  fn ensure_read_permission(
    &self,
    permissions: &dyn NodePermissions,
    path: &Path,
  ) -> Result<(), AnyError>;
}

pub static NODE_ENV_VAR_ALLOWLIST: Lazy<HashSet<String>> = Lazy::new(|| {
  // The full list of environment variables supported by Node.js is available
  // at https://nodejs.org/api/cli.html#environment-variables
  let mut set = HashSet::new();
  set.insert("NODE_DEBUG".to_string());
  set.insert("NODE_OPTIONS".to_string());
  set
});

#[op2]
#[string]
fn op_node_build_os() -> String {
  env!("TARGET").split('-').nth(2).unwrap().to_string()
}

#[op2(fast)]
fn op_is_any_arraybuffer(value: &v8::Value) -> bool {
  value.is_array_buffer() || value.is_shared_array_buffer()
}

#[op2(fast)]
fn op_node_is_promise_rejected(value: v8::Local<v8::Value>) -> bool {
  let Ok(promise) = v8::Local::<v8::Promise>::try_from(value) else {
    return false;
  };

  promise.state() == v8::PromiseState::Rejected
}

#[op2]
#[string]
fn op_npm_process_state(state: &mut OpState) -> Result<String, AnyError> {
  let npm_resolver = state.borrow_mut::<NpmResolverRc>();
  Ok(npm_resolver.get_npm_process_state())
}

deno_core::extension!(deno_node,
  deps = [ deno_io, deno_fs ],
  parameters = [P: NodePermissions],
  ops = [
    ops::crypto::op_node_create_decipheriv,
    ops::crypto::op_node_cipheriv_encrypt,
    ops::crypto::op_node_cipheriv_final,
    ops::crypto::op_node_cipheriv_set_aad,
    ops::crypto::op_node_decipheriv_set_aad,
    ops::crypto::op_node_create_cipheriv,
    ops::crypto::op_node_create_hash,
    ops::crypto::op_node_get_hashes,
    ops::crypto::op_node_decipheriv_decrypt,
    ops::crypto::op_node_decipheriv_final,
    ops::crypto::op_node_hash_update,
    ops::crypto::op_node_hash_update_str,
    ops::crypto::op_node_hash_digest,
    ops::crypto::op_node_hash_digest_hex,
    ops::crypto::op_node_hash_clone,
    ops::crypto::op_node_private_encrypt,
    ops::crypto::op_node_private_decrypt,
    ops::crypto::op_node_public_encrypt,
    ops::crypto::op_node_check_prime,
    ops::crypto::op_node_check_prime_async,
    ops::crypto::op_node_check_prime_bytes,
    ops::crypto::op_node_check_prime_bytes_async,
    ops::crypto::op_node_gen_prime,
    ops::crypto::op_node_gen_prime_async,
    ops::crypto::op_node_pbkdf2,
    ops::crypto::op_node_pbkdf2_async,
    ops::crypto::op_node_hkdf,
    ops::crypto::op_node_hkdf_async,
    ops::crypto::op_node_generate_secret,
    ops::crypto::op_node_generate_secret_async,
    ops::crypto::op_node_sign,
    ops::crypto::op_node_generate_rsa,
    ops::crypto::op_node_generate_rsa_async,
    ops::crypto::op_node_dsa_generate,
    ops::crypto::op_node_dsa_generate_async,
    ops::crypto::op_node_ec_generate,
    ops::crypto::op_node_ec_generate_async,
    ops::crypto::op_node_ed25519_generate,
    ops::crypto::op_node_ed25519_generate_async,
    ops::crypto::op_node_x25519_generate,
    ops::crypto::op_node_x25519_generate_async,
    ops::crypto::op_node_dh_generate_group,
    ops::crypto::op_node_dh_generate_group_async,
    ops::crypto::op_node_dh_generate,
    ops::crypto::op_node_dh_generate2,
    ops::crypto::op_node_dh_compute_secret,
    ops::crypto::op_node_dh_generate_async,
    ops::crypto::op_node_verify,
    ops::crypto::op_node_random_int,
    ops::crypto::op_node_scrypt_sync,
    ops::crypto::op_node_scrypt_async,
    ops::crypto::op_node_ecdh_generate_keys,
    ops::crypto::op_node_ecdh_compute_secret,
    ops::crypto::op_node_ecdh_compute_public_key,
    ops::crypto::x509::op_node_x509_parse,
    ops::crypto::x509::op_node_x509_ca,
    ops::crypto::x509::op_node_x509_check_email,
    ops::crypto::x509::op_node_x509_fingerprint,
    ops::crypto::x509::op_node_x509_fingerprint256,
    ops::crypto::x509::op_node_x509_fingerprint512,
    ops::crypto::x509::op_node_x509_get_issuer,
    ops::crypto::x509::op_node_x509_get_subject,
    ops::crypto::x509::op_node_x509_get_valid_from,
    ops::crypto::x509::op_node_x509_get_valid_to,
    ops::crypto::x509::op_node_x509_get_serial_number,
    ops::crypto::x509::op_node_x509_key_usage,
    ops::winerror::op_node_sys_to_uv_error,
    ops::v8::op_v8_cached_data_version_tag,
    ops::v8::op_v8_get_heap_statistics,
    ops::idna::op_node_idna_domain_to_ascii,
    ops::idna::op_node_idna_domain_to_unicode,
    ops::idna::op_node_idna_punycode_decode,
    ops::idna::op_node_idna_punycode_encode,
    ops::zlib::op_zlib_new,
    ops::zlib::op_zlib_close,
    ops::zlib::op_zlib_close_if_pending,
    ops::zlib::op_zlib_write,
    ops::zlib::op_zlib_write_async,
    ops::zlib::op_zlib_init,
    ops::zlib::op_zlib_reset,
    ops::zlib::brotli::op_brotli_compress,
    ops::zlib::brotli::op_brotli_compress_async,
    ops::zlib::brotli::op_create_brotli_compress,
    ops::zlib::brotli::op_brotli_compress_stream,
    ops::zlib::brotli::op_brotli_compress_stream_end,
    ops::zlib::brotli::op_brotli_decompress,
    ops::zlib::brotli::op_brotli_decompress_async,
    ops::zlib::brotli::op_create_brotli_decompress,
    ops::zlib::brotli::op_brotli_decompress_stream,
    ops::zlib::brotli::op_brotli_decompress_stream_end,
    ops::http::op_node_http_request<P>,
    ops::http2::op_http2_connect,
    ops::http2::op_http2_poll_client_connection,
    ops::http2::op_http2_client_request,
    ops::http2::op_http2_client_get_response,
    ops::http2::op_http2_client_get_response_body_chunk,
    ops::http2::op_http2_client_send_data,
    ops::http2::op_http2_client_end_stream,
    ops::http2::op_http2_client_reset_stream,
    ops::http2::op_http2_client_send_trailers,
    ops::http2::op_http2_client_get_response_trailers,
    ops::http2::op_http2_accept,
    ops::http2::op_http2_listen,
    ops::http2::op_http2_send_response,
    ops::os::op_node_os_get_priority<P>,
    ops::os::op_node_os_set_priority<P>,
    ops::os::op_node_os_username<P>,
    ops::os::op_geteuid<P>,
    op_node_build_os,
    op_is_any_arraybuffer,
    op_node_is_promise_rejected,
    op_npm_process_state,
    ops::require::op_require_init_paths,
    ops::require::op_require_node_module_paths<P>,
    ops::require::op_require_proxy_path,
    ops::require::op_require_is_deno_dir_package,
    ops::require::op_require_resolve_deno_dir,
    ops::require::op_require_is_request_relative,
    ops::require::op_require_resolve_lookup_paths,
    ops::require::op_require_try_self_parent_path<P>,
    ops::require::op_require_try_self<P>,
    ops::require::op_require_real_path<P>,
    ops::require::op_require_path_is_absolute,
    ops::require::op_require_path_dirname,
    ops::require::op_require_stat<P>,
    ops::require::op_require_path_resolve,
    ops::require::op_require_path_basename,
    ops::require::op_require_read_file<P>,
    ops::require::op_require_as_file_path,
    ops::require::op_require_resolve_exports<P>,
    ops::require::op_require_read_closest_package_json<P>,
    ops::require::op_require_read_package_scope<P>,
    ops::require::op_require_package_imports_resolve<P>,
    ops::require::op_require_break_on_next_statement,
    ops::util::op_node_guess_handle_type,
    ops::crypto::op_node_create_private_key,
  ],
  esm_entry_point = "ext:deno_node/02_init.js",
  esm = [
    dir "polyfills",
    "00_globals.js",
    "02_init.js",
    "_brotli.js",
    "_events.mjs",
    "_fs/_fs_access.ts",
    "_fs/_fs_appendFile.ts",
    "_fs/_fs_chmod.ts",
    "_fs/_fs_chown.ts",
    "_fs/_fs_close.ts",
    "_fs/_fs_common.ts",
    "_fs/_fs_constants.ts",
    "_fs/_fs_copy.ts",
    "_fs/_fs_dir.ts",
    "_fs/_fs_dirent.ts",
    "_fs/_fs_exists.ts",
    "_fs/_fs_fdatasync.ts",
    "_fs/_fs_fstat.ts",
    "_fs/_fs_fsync.ts",
    "_fs/_fs_ftruncate.ts",
    "_fs/_fs_futimes.ts",
    "_fs/_fs_link.ts",
    "_fs/_fs_lstat.ts",
    "_fs/_fs_mkdir.ts",
    "_fs/_fs_mkdtemp.ts",
    "_fs/_fs_open.ts",
    "_fs/_fs_opendir.ts",
    "_fs/_fs_read.ts",
    "_fs/_fs_readdir.ts",
    "_fs/_fs_readFile.ts",
    "_fs/_fs_readlink.ts",
    "_fs/_fs_realpath.ts",
    "_fs/_fs_rename.ts",
    "_fs/_fs_rm.ts",
    "_fs/_fs_rmdir.ts",
    "_fs/_fs_stat.ts",
    "_fs/_fs_symlink.ts",
    "_fs/_fs_truncate.ts",
    "_fs/_fs_unlink.ts",
    "_fs/_fs_utimes.ts",
    "_fs/_fs_watch.ts",
    "_fs/_fs_write.mjs",
    "_fs/_fs_writeFile.ts",
    "_fs/_fs_writev.mjs",
    "_http_agent.mjs",
    "_http_common.ts",
    "_http_outgoing.ts",
    "_next_tick.ts",
    "_process/exiting.ts",
    "_process/process.ts",
    "_process/streams.mjs",
    "_readline.mjs",
    "_stream.mjs",
    "_tls_common.ts",
    "_tls_wrap.ts",
    "_util/_util_callbackify.ts",
    "_util/asserts.ts",
    "_util/async.ts",
    "_util/os.ts",
    "_util/std_asserts.ts",
    "_util/std_fmt_colors.ts",
    "_util/std_testing_diff.ts",
    "_utils.ts",
    "_zlib_binding.mjs",
    "_zlib.mjs",
    "assertion_error.ts",
    "inspector.ts",
    "internal_binding/_libuv_winerror.ts",
    "internal_binding/_listen.ts",
    "internal_binding/_node.ts",
    "internal_binding/_timingSafeEqual.ts",
    "internal_binding/_utils.ts",
    "internal_binding/ares.ts",
    "internal_binding/async_wrap.ts",
    "internal_binding/buffer.ts",
    "internal_binding/cares_wrap.ts",
    "internal_binding/connection_wrap.ts",
    "internal_binding/constants.ts",
    "internal_binding/crypto.ts",
    "internal_binding/handle_wrap.ts",
    "internal_binding/mod.ts",
    "internal_binding/node_file.ts",
    "internal_binding/node_options.ts",
    "internal_binding/pipe_wrap.ts",
    "internal_binding/stream_wrap.ts",
    "internal_binding/string_decoder.ts",
    "internal_binding/symbols.ts",
    "internal_binding/tcp_wrap.ts",
    "internal_binding/types.ts",
    "internal_binding/udp_wrap.ts",
    "internal_binding/util.ts",
    "internal_binding/uv.ts",
    "internal/assert.mjs",
    "internal/async_hooks.ts",
    "internal/buffer.mjs",
    "internal/child_process.ts",
    "internal/cli_table.ts",
    "internal/console/constructor.mjs",
    "internal/constants.ts",
    "internal/crypto/_keys.ts",
    "internal/crypto/_randomBytes.ts",
    "internal/crypto/_randomFill.mjs",
    "internal/crypto/_randomInt.ts",
    "internal/crypto/certificate.ts",
    "internal/crypto/cipher.ts",
    "internal/crypto/constants.ts",
    "internal/crypto/diffiehellman.ts",
    "internal/crypto/hash.ts",
    "internal/crypto/hkdf.ts",
    "internal/crypto/keygen.ts",
    "internal/crypto/keys.ts",
    "internal/crypto/pbkdf2.ts",
    "internal/crypto/random.ts",
    "internal/crypto/scrypt.ts",
    "internal/crypto/sig.ts",
    "internal/crypto/util.ts",
    "internal/crypto/x509.ts",
    "internal/dgram.ts",
    "internal/dns/promises.ts",
    "internal/dns/utils.ts",
    "internal/dtrace.ts",
    "internal/error_codes.ts",
    "internal/errors.ts",
    "internal/event_target.mjs",
    "internal/fixed_queue.ts",
    "internal/fs/streams.mjs",
    "internal/fs/utils.mjs",
    "internal/fs/handle.ts",
    "internal/hide_stack_frames.ts",
    "internal/http.ts",
    "internal/idna.ts",
    "internal/net.ts",
    "internal/normalize_encoding.mjs",
    "internal/options.ts",
    "internal/primordials.mjs",
    "internal/process/per_thread.mjs",
    "internal/querystring.ts",
    "internal/readline/callbacks.mjs",
    "internal/readline/emitKeypressEvents.mjs",
    "internal/readline/interface.mjs",
    "internal/readline/promises.mjs",
    "internal/readline/symbols.mjs",
    "internal/readline/utils.mjs",
    "internal/stream_base_commons.ts",
    "internal/streams/add-abort-signal.mjs",
    "internal/streams/buffer_list.mjs",
    "internal/streams/destroy.mjs",
    "internal/streams/duplex.mjs",
    "internal/streams/end-of-stream.mjs",
    "internal/streams/lazy_transform.mjs",
    "internal/streams/passthrough.mjs",
    "internal/streams/readable.mjs",
    "internal/streams/state.mjs",
    "internal/streams/transform.mjs",
    "internal/streams/utils.mjs",
    "internal/streams/writable.mjs",
    "internal/test/binding.ts",
    "internal/timers.mjs",
    "internal/url.ts",
    "internal/util.mjs",
    "internal/util/comparisons.ts",
    "internal/util/debuglog.ts",
    "internal/util/inspect.mjs",
    "internal/util/types.ts",
    "internal/validators.mjs",
    "path/_constants.ts",
    "path/_interface.ts",
    "path/_util.ts",
    "path/_posix.ts",
    "path/_win32.ts",
    "path/common.ts",
    "path/mod.ts",
    "path/separator.ts",
    "readline/promises.ts",
    "wasi.ts",
    "assert.ts" with_specifier "node:assert",
    "assert/strict.ts" with_specifier "node:assert/strict",
    "async_hooks.ts" with_specifier "node:async_hooks",
    "buffer.ts" with_specifier "node:buffer",
    "child_process.ts" with_specifier "node:child_process",
    "cluster.ts" with_specifier "node:cluster",
    "console.ts" with_specifier "node:console",
    "constants.ts" with_specifier "node:constants",
    "crypto.ts" with_specifier "node:crypto",
    "dgram.ts" with_specifier "node:dgram",
    "diagnostics_channel.ts" with_specifier "node:diagnostics_channel",
    "dns.ts" with_specifier "node:dns",
    "dns/promises.ts" with_specifier "node:dns/promises",
    "domain.ts" with_specifier "node:domain",
    "events.ts" with_specifier "node:events",
    "fs.ts" with_specifier "node:fs",
    "fs/promises.ts" with_specifier "node:fs/promises",
    "http.ts" with_specifier "node:http",
    "http2.ts" with_specifier "node:http2",
    "https.ts" with_specifier "node:https",
    "01_require.js" with_specifier "node:module",
    "net.ts" with_specifier "node:net",
    "os.ts" with_specifier "node:os",
    "path.ts" with_specifier "node:path",
    "path/posix.ts" with_specifier "node:path/posix",
    "path/win32.ts" with_specifier "node:path/win32",
    "perf_hooks.ts" with_specifier "node:perf_hooks",
    "process.ts" with_specifier "node:process",
    "punycode.ts" with_specifier "node:punycode",
    "querystring.ts" with_specifier "node:querystring",
    "readline.ts" with_specifier "node:readline",
    "repl.ts" with_specifier "node:repl",
    "stream.ts" with_specifier "node:stream",
    "stream/consumers.mjs" with_specifier "node:stream/consumers",
    "stream/promises.mjs" with_specifier "node:stream/promises",
    "stream/web.ts" with_specifier "node:stream/web",
    "string_decoder.ts" with_specifier "node:string_decoder",
    "sys.ts" with_specifier "node:sys",
    "testing.ts" with_specifier "node:test",
    "timers.ts" with_specifier "node:timers",
    "timers/promises.ts" with_specifier "node:timers/promises",
    "tls.ts" with_specifier "node:tls",
    "tty.js" with_specifier "node:tty",
    "url.ts" with_specifier "node:url",
    "util.ts" with_specifier "node:util",
    "util/types.ts" with_specifier "node:util/types",
    "v8.ts" with_specifier "node:v8",
    "vm.ts" with_specifier "node:vm",
    "worker_threads.ts" with_specifier "node:worker_threads",
    "zlib.ts" with_specifier "node:zlib",
  ],
  options = {
    maybe_npm_resolver: Option<NpmResolverRc>,
    fs: deno_fs::FileSystemRc,
  },
  state = |state, options| {
    let fs = options.fs;
    state.put(fs.clone());
    if let Some(npm_resolver) = options.maybe_npm_resolver {
      state.put(npm_resolver.clone());
      state.put(Rc::new(NodeResolver::new(
        fs,
        npm_resolver,
      )))
    }
  },
  global_template_middleware = global_template_middleware,
  global_object_middleware = global_object_middleware,
  customizer = |ext: &mut deno_core::Extension| {
    let mut external_references = Vec::with_capacity(7);

    global::GETTER_MAP_FN.with(|getter| {
      external_references.push(ExternalReference {
        named_getter: *getter,
      });
    });
    global::SETTER_MAP_FN.with(|setter| {
      external_references.push(ExternalReference {
        named_setter: *setter,
      });
    });
    global::QUERY_MAP_FN.with(|query| {
      external_references.push(ExternalReference {
        named_getter: *query,
      });
    });
    global::DELETER_MAP_FN.with(|deleter| {
      external_references.push(ExternalReference {
        named_getter: *deleter,
      },);
    });
    global::ENUMERATOR_MAP_FN.with(|enumerator| {
      external_references.push(ExternalReference {
        enumerator: *enumerator,
      });
    });
    global::DEFINER_MAP_FN.with(|definer| {
      external_references.push(ExternalReference {
        named_definer: *definer,
      });
    });
    global::DESCRIPTOR_MAP_FN.with(|descriptor| {
      external_references.push(ExternalReference {
        named_getter: *descriptor,
      });
    });
    ext.external_references.to_mut().extend(external_references);
  },
);

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(moduleName, isMain, inspectBrk) {{
      Deno[Deno.internal].node.loadCjsModule(moduleName, isMain, inspectBrk);
    }})('{module}', {main}, {inspect_brk});"#,
    main = main,
    module = escape_for_single_quote_string(module),
    inspect_brk = inspect_brk,
  )
  .into();

  js_runtime.execute_script(located_script_name!(), source_code)?;
  Ok(())
}