// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
use deno_core::anyhow::Context;
use deno_core::error::generic_error;
use deno_core::error::AnyError;
use deno_core::normalize_path;
use deno_core::op2;
use deno_core::url::Url;
use deno_core::JsRuntimeInspector;
use deno_core::ModuleSpecifier;
use deno_core::OpState;
use deno_fs::FileSystemRc;
use std::cell::RefCell;
use std::path::Path;
use std::path::PathBuf;
use std::rc::Rc;
use crate::resolution;
use crate::NodeModuleKind;
use crate::NodePermissions;
use crate::NodeResolutionMode;
use crate::NodeResolver;
use crate::NpmResolverRc;
use crate::PackageJson;
fn ensure_read_permission
(
state: &mut OpState,
file_path: &Path,
) -> Result<(), AnyError>
where
P: NodePermissions + 'static,
{
let resolver = state.borrow::();
let permissions = state.borrow::
();
resolver.ensure_read_permission(permissions, file_path)
}
#[op2]
#[serde]
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![]
}
#[op2]
#[serde]
pub fn op_require_node_module_paths
(
state: &mut OpState,
#[string] from: String,
) -> Result, AnyError>
where
P: NodePermissions + 'static,
{
let fs = state.borrow::();
// Guarantee that "from" is absolute.
let from_url = if from.starts_with("file:///") {
Url::parse(&from)?
} else {
deno_core::resolve_path(
&from,
&(fs.cwd().map_err(AnyError::from)).context("Unable to get CWD")?,
)?
};
let from = url_to_file_path(&from_url)?;
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();
}
Ok(paths)
}
#[op2]
#[string]
pub fn op_require_proxy_path(#[string] 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
}
}
#[op2(fast)]
pub fn op_require_is_request_relative(#[string] 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
}
#[op2]
#[string]
pub fn op_require_resolve_deno_dir(
state: &mut OpState,
#[string] request: String,
#[string] parent_filename: String,
) -> Option {
let resolver = state.borrow::();
resolver
.resolve_package_folder_from_package(
&request,
&ModuleSpecifier::from_file_path(&parent_filename).unwrap_or_else(|_| {
panic!("Url::from_file_path: [{:?}]", parent_filename)
}),
NodeResolutionMode::Execution,
)
.ok()
.map(|p| p.to_string_lossy().to_string())
}
#[op2(fast)]
pub fn op_require_is_deno_dir_package(
state: &mut OpState,
#[string] path: String,
) -> bool {
let resolver = state.borrow::();
resolver.in_npm_package_at_file_path(&PathBuf::from(path))
}
#[op2]
#[serde]
pub fn op_require_resolve_lookup_paths(
#[string] request: String,
#[serde] maybe_parent_paths: Option>,
#[string] 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()])
}
#[op2(fast)]
pub fn op_require_path_is_absolute(#[string] p: String) -> bool {
PathBuf::from(p).is_absolute()
}
#[op2(fast)]
pub fn op_require_stat
(
state: &mut OpState,
#[string] path: String,
) -> Result
where
P: NodePermissions + 'static,
{
let path = PathBuf::from(path);
ensure_read_permission::
(state, &path)?;
let fs = state.borrow::();
if let Ok(metadata) = fs.stat_sync(&path) {
if metadata.is_file {
return Ok(0);
} else {
return Ok(1);
}
}
Ok(-1)
}
#[op2]
#[string]
pub fn op_require_real_path
(
state: &mut OpState,
#[string] request: String,
) -> Result
where
P: NodePermissions + 'static,
{
let path = PathBuf::from(request);
ensure_read_permission::
(state, &path)?;
let fs = state.borrow::();
let canonicalized_path =
deno_core::strip_unc_prefix(fs.realpath_sync(&path)?);
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()
}
#[op2]
#[string]
pub fn op_require_path_resolve(#[serde] parts: Vec) -> String {
path_resolve(parts)
}
#[op2]
#[string]
pub fn op_require_path_dirname(
#[string] 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"))
}
}
#[op2]
#[string]
pub fn op_require_path_basename(
#[string] 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"))
}
}
#[op2]
#[string]
pub fn op_require_try_self_parent_path