diff options
author | David Sherret <dsherret@users.noreply.github.com> | 2022-08-20 11:31:33 -0400 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-08-20 11:31:33 -0400 |
commit | 87f80ff6be5e2a132b583a9c380fff5db3cb2b07 (patch) | |
tree | b2c43c9004b9ada204f3b5db7c221fa6322f026a /ext/node/lib.rs | |
parent | 1ffbd561642d05a05e18ada764d50581dea779ef (diff) |
feat(unstable): initial support for npm specifiers (#15484)
Co-authored-by: Bartek IwaĆczuk <biwanczuk@gmail.com>
Diffstat (limited to 'ext/node/lib.rs')
-rw-r--r-- | ext/node/lib.rs | 380 |
1 files changed, 298 insertions, 82 deletions
diff --git a/ext/node/lib.rs b/ext/node/lib.rs index c61e27079..6be376e6d 100644 --- a/ext/node/lib.rs +++ b/ext/node/lib.rs @@ -4,22 +4,60 @@ 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::Extension; use deno_core::OpState; +use std::path::Path; use std::path::PathBuf; +use std::rc::Rc; + +pub use package_json::PackageJson; +pub use resolution::get_package_scope_config; +pub use resolution::legacy_main_resolve; +pub use resolution::package_exports_resolve; +pub use resolution::package_imports_resolve; +pub use resolution::package_resolve; +pub use resolution::DEFAULT_CONDITIONS; + +pub trait DenoDirNpmResolver { + fn resolve_package_folder_from_package( + &self, + specifier: &str, + referrer: &Path, + ) -> Result<PathBuf, AnyError>; + + fn resolve_package_folder_from_path( + &self, + path: &Path, + ) -> Result<PathBuf, AnyError>; + + fn in_npm_package(&self, path: &Path) -> bool; + + fn ensure_read_permission(&self, path: &Path) -> Result<(), AnyError>; +} + +mod errors; +mod package_json; +mod resolution; -pub struct Unstable(pub bool); +struct Unstable(pub bool); -pub fn init(unstable: bool) -> Extension { +pub fn init( + unstable: bool, + maybe_npm_resolver: Option<Rc<dyn DenoDirNpmResolver>>, +) -> Extension { Extension::builder() .js(include_js_files!( prefix "deno:ext/node", - "01_require.js", + "01_node.js", + "02_require.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(), @@ -31,9 +69,16 @@ pub fn init(unstable: bool) -> Extension { 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_package_scope::decl(), + op_require_package_imports_resolve::decl(), ]) .state(move |state| { state.put(Unstable(unstable)); + if let Some(npm_resolver) = maybe_npm_resolver.clone() { + state.put(npm_resolver); + } Ok(()) }) .build() @@ -48,60 +93,74 @@ fn check_unstable(state: &OpState) { } } +fn ensure_read_permission( + state: &mut OpState, + file_path: &Path, +) -> Result<(), AnyError> { + let resolver = { + let resolver = state.borrow::<Rc<dyn DenoDirNpmResolver>>(); + resolver.clone() + }; + resolver.ensure_read_permission(file_path) +} + #[op] pub fn op_require_init_paths(state: &mut OpState) -> Vec<String> { check_unstable(state); - 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("..") - } + // 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")]; + // 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")); - } + // 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<String> = node_path - .split(delimiter) - .filter(|e| !e.is_empty()) - .map(|s| s.to_string()) - .collect(); - node_paths.append(&mut paths); - paths = node_paths; - } + // 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<String> = node_path + // .split(delimiter) + // .filter(|e| !e.is_empty()) + // .map(|s| s.to_string()) + // .collect(); + // node_paths.append(&mut paths); + // paths = node_paths; + // } - paths + vec![] } #[op] pub fn op_require_node_module_paths( state: &mut OpState, from: String, -) -> Vec<String> { +) -> Result<Vec<String>, AnyError> { check_unstable(state); // Guarantee that "from" is absolute. let from = deno_core::resolve_path(&from) @@ -109,6 +168,8 @@ pub fn op_require_node_module_paths( .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(); @@ -117,14 +178,14 @@ pub fn op_require_node_module_paths( if bytes[from_str.len() - 1] == b'\\' && bytes[from_str.len() - 2] == b':' { let p = from_str.to_owned() + "node_modules"; - return vec![p]; + 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 vec!["/node_modules".to_string()]; + return Ok(vec!["/node_modules".to_string()]); } } @@ -144,7 +205,7 @@ pub fn op_require_node_module_paths( paths.push("/node_modules".to_string()); } - paths + Ok(paths) } #[op] @@ -171,11 +232,8 @@ fn op_require_is_request_relative( request: String, ) -> bool { check_unstable(state); - if request.starts_with("./") { - return true; - } - - if request.starts_with("../") { + if request.starts_with("./") || request.starts_with("../") || request == ".." + { return true; } @@ -193,6 +251,30 @@ fn op_require_is_request_relative( } #[op] +fn op_require_resolve_deno_dir( + state: &mut OpState, + request: String, + parent_filename: String, +) -> Option<String> { + check_unstable(state); + let resolver = state.borrow::<Rc<dyn DenoDirNpmResolver>>(); + resolver + .resolve_package_folder_from_package( + &request, + &PathBuf::from(parent_filename), + ) + .ok() + .map(|p| p.to_string_lossy().to_string()) +} + +#[op] +fn op_require_is_deno_dir_package(state: &mut OpState, path: String) -> bool { + check_unstable(state); + let resolver = state.borrow::<Rc<dyn DenoDirNpmResolver>>(); + resolver.in_npm_package(&PathBuf::from(path)) +} + +#[op] fn op_require_resolve_lookup_paths( state: &mut OpState, request: String, @@ -242,17 +324,19 @@ fn op_require_path_is_absolute(state: &mut OpState, p: String) -> bool { } #[op] -fn op_require_stat(state: &mut OpState, filename: String) -> i32 { +fn op_require_stat(state: &mut OpState, path: String) -> Result<i32, AnyError> { check_unstable(state); - if let Ok(metadata) = std::fs::metadata(&filename) { + let path = PathBuf::from(path); + ensure_read_permission(state, &path)?; + if let Ok(metadata) = std::fs::metadata(&path) { if metadata.is_file() { - return 0; + return Ok(0); } else { - return 1; + return Ok(1); } } - -1 + Ok(-1) } #[op] @@ -261,7 +345,9 @@ fn op_require_real_path( request: String, ) -> Result<String, AnyError> { check_unstable(state); - let mut canonicalized_path = PathBuf::from(request).canonicalize()?; + 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 @@ -273,9 +359,7 @@ fn op_require_real_path( Ok(canonicalized_path.to_string_lossy().to_string()) } -#[op] -fn op_require_path_resolve(state: &mut OpState, parts: Vec<String>) -> String { - check_unstable(state); +fn path_resolve(parts: Vec<String>) -> String { assert!(!parts.is_empty()); let mut p = PathBuf::from(&parts[0]); if parts.len() > 1 { @@ -287,6 +371,12 @@ fn op_require_path_resolve(state: &mut OpState, parts: Vec<String>) -> String { } #[op] +fn op_require_path_resolve(state: &mut OpState, parts: Vec<String>) -> String { + check_unstable(state); + path_resolve(parts) +} + +#[op] fn op_require_path_dirname(state: &mut OpState, request: String) -> String { check_unstable(state); let p = PathBuf::from(request); @@ -306,57 +396,183 @@ fn op_require_try_self_parent_path( has_parent: bool, maybe_parent_filename: Option<String>, maybe_parent_id: Option<String>, -) -> Option<String> { +) -> Result<Option<String>, AnyError> { check_unstable(state); if !has_parent { - return None; + return Ok(None); } if let Some(parent_filename) = maybe_parent_filename { - return Some(parent_filename); + return Ok(Some(parent_filename)); } if let Some(parent_id) = maybe_parent_id { if parent_id == "<repl>" || parent_id == "internal/preload" { if let Ok(cwd) = std::env::current_dir() { - return Some(cwd.to_string_lossy().to_string()); + ensure_read_permission(state, &cwd)?; + return Ok(Some(cwd.to_string_lossy().to_string())); } } } - None + Ok(None) } #[op] fn op_require_try_self( state: &mut OpState, - has_parent: bool, - maybe_parent_filename: Option<String>, - maybe_parent_id: Option<String>, -) -> Option<String> { + parent_path: Option<String>, + request: String, +) -> Result<Option<String>, AnyError> { check_unstable(state); - if !has_parent { - return None; + if parent_path.is_none() { + return Ok(None); } - if let Some(parent_filename) = maybe_parent_filename { - return Some(parent_filename); + let resolver = state.borrow::<Rc<dyn DenoDirNpmResolver>>().clone(); + let pkg = resolution::get_package_scope_config( + &Url::from_file_path(parent_path.unwrap()).unwrap(), + &*resolver, + ) + .ok(); + if pkg.is_none() { + return Ok(None); } - if let Some(parent_id) = maybe_parent_id { - if parent_id == "<repl>" || parent_id == "internal/preload" { - if let Ok(cwd) = std::env::current_dir() { - return Some(cwd.to_string_lossy().to_string()); - } - } + 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 base = deno_core::url::Url::from_file_path(PathBuf::from("/")).unwrap(); + if let Some(exports) = &pkg.exports { + resolution::package_exports_resolve( + deno_core::url::Url::from_file_path(&pkg.path).unwrap(), + expansion, + exports, + &base, + resolution::REQUIRE_CONDITIONS, + &*resolver, + ) + .map(|r| Some(r.as_str().to_string())) + } else { + Ok(None) } - None } #[op] fn op_require_read_file( state: &mut OpState, - _filename: String, + file_path: String, ) -> Result<String, AnyError> { check_unstable(state); - todo!("not implemented"); + 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( + state: &mut OpState, + file_or_url: String, +) -> String { + check_unstable(state); + match Url::parse(&file_or_url) { + Ok(url) => url.to_file_path().unwrap().to_string_lossy().to_string(), + Err(_) => file_or_url, + } +} + +#[op] +fn op_require_resolve_exports( + state: &mut OpState, + modules_path: String, + _request: String, + name: String, + expansion: String, + parent_path: String, +) -> Result<Option<String>, AnyError> { + check_unstable(state); + let resolver = state.borrow::<Rc<dyn DenoDirNpmResolver>>().clone(); + + let pkg_path = if resolver.in_npm_package(&PathBuf::from(&modules_path)) { + modules_path + } else { + path_resolve(vec![modules_path, name]) + }; + let pkg = PackageJson::load( + &*resolver, + PathBuf::from(&pkg_path).join("package.json"), + )?; + + if let Some(exports) = &pkg.exports { + let base = Url::from_file_path(parent_path).unwrap(); + resolution::package_exports_resolve( + deno_core::url::Url::from_directory_path(pkg_path).unwrap(), + format!(".{}", expansion), + exports, + &base, + resolution::REQUIRE_CONDITIONS, + &*resolver, + ) + .map(|r| Some(r.to_file_path().unwrap().to_string_lossy().to_string())) + } else { + Ok(None) + } +} + +#[op] +fn op_require_read_package_scope( + state: &mut OpState, + filename: String, +) -> Option<PackageJson> { + check_unstable(state); + let resolver = state.borrow::<Rc<dyn DenoDirNpmResolver>>().clone(); + resolution::get_package_scope_config( + &Url::from_file_path(filename).unwrap(), + &*resolver, + ) + .ok() +} + +#[op] +fn op_require_package_imports_resolve( + state: &mut OpState, + parent_filename: String, + request: String, +) -> Result<Option<String>, AnyError> { + check_unstable(state); + let parent_path = PathBuf::from(&parent_filename); + ensure_read_permission(state, &parent_path)?; + let resolver = state.borrow::<Rc<dyn DenoDirNpmResolver>>().clone(); + let pkg = PackageJson::load(&*resolver, 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, + resolution::REQUIRE_CONDITIONS, + &*resolver, + ) + .map(|r| Some(r.as_str().to_string())); + state.put(resolver); + r + } else { + Ok(None) + } } |