summaryrefslogtreecommitdiff
path: root/ext/node/package_json.rs
diff options
context:
space:
mode:
authorDavid Sherret <dsherret@users.noreply.github.com>2024-06-26 17:24:10 -0400
committerGitHub <noreply@github.com>2024-06-26 21:24:10 +0000
commit0da01c0ca6b537f74be32126e567bdfc2c73ed16 (patch)
treeef29d32cffb03a975a58c16827b0691dda50a5b3 /ext/node/package_json.rs
parent86e0292733d6d08bf338b68fd50863aef17b1e44 (diff)
refactor: move PackageJson to deno_config (#24348)
Diffstat (limited to 'ext/node/package_json.rs')
-rw-r--r--ext/node/package_json.rs288
1 files changed, 37 insertions, 251 deletions
diff --git a/ext/node/package_json.rs b/ext/node/package_json.rs
index a19a2d64d..8a88fe8f1 100644
--- a/ext/node/package_json.rs
+++ b/ext/node/package_json.rs
@@ -1,272 +1,58 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
-use crate::NodeModuleKind;
-use crate::NodePermissions;
-
-use super::NpmResolver;
-
-use deno_core::anyhow;
-use deno_core::anyhow::bail;
-use deno_core::error::AnyError;
-use deno_core::serde_json;
-use deno_core::serde_json::Map;
-use deno_core::serde_json::Value;
-use deno_core::ModuleSpecifier;
-use indexmap::IndexMap;
-use serde::Serialize;
+use deno_config::package_json::PackageJson;
+use deno_config::package_json::PackageJsonLoadError;
+use deno_config::package_json::PackageJsonRc;
+use deno_fs::DenoConfigFsAdapter;
use std::cell::RefCell;
use std::collections::HashMap;
use std::io::ErrorKind;
+use std::path::Path;
use std::path::PathBuf;
-use std::rc::Rc;
+// use a thread local cache so that workers have their own distinct cache
thread_local! {
- static CACHE: RefCell<HashMap<PathBuf, Rc<PackageJson>>> = RefCell::new(HashMap::new());
+ static CACHE: RefCell<HashMap<PathBuf, PackageJsonRc>> = RefCell::new(HashMap::new());
}
-#[derive(Clone, Debug, Serialize)]
-pub struct PackageJson {
- pub exists: bool,
- pub exports: Option<Map<String, Value>>,
- pub imports: Option<Map<String, Value>>,
- pub bin: Option<Value>,
- main: Option<String>, // use .main(...)
- module: Option<String>, // use .main(...)
- pub name: Option<String>,
- pub version: Option<String>,
- pub path: PathBuf,
- pub typ: String,
- pub types: Option<String>,
- pub dependencies: Option<IndexMap<String, String>>,
- pub dev_dependencies: Option<IndexMap<String, String>>,
- pub scripts: Option<IndexMap<String, String>>,
-}
-
-impl PackageJson {
- pub fn empty(path: PathBuf) -> PackageJson {
- PackageJson {
- exists: false,
- exports: None,
- imports: None,
- bin: None,
- main: None,
- module: None,
- name: None,
- version: None,
- path,
- typ: "none".to_string(),
- types: None,
- dependencies: None,
- dev_dependencies: None,
- scripts: None,
- }
- }
-
- pub fn load(
- fs: &dyn deno_fs::FileSystem,
- resolver: &dyn NpmResolver,
- permissions: &mut dyn NodePermissions,
- path: PathBuf,
- ) -> Result<Rc<PackageJson>, AnyError> {
- resolver.ensure_read_permission(permissions, &path)?;
- Self::load_skip_read_permission(fs, path)
- }
-
- pub fn load_skip_read_permission(
- fs: &dyn deno_fs::FileSystem,
- path: PathBuf,
- ) -> Result<Rc<PackageJson>, AnyError> {
- assert!(path.is_absolute());
-
- if CACHE.with(|cache| cache.borrow().contains_key(&path)) {
- return Ok(CACHE.with(|cache| cache.borrow()[&path].clone()));
- }
-
- let source = match fs.read_text_file_lossy_sync(&path, None) {
- Ok(source) => source,
- Err(err) if err.kind() == ErrorKind::NotFound => {
- return Ok(Rc::new(PackageJson::empty(path)));
- }
- Err(err) => bail!(
- "Error loading package.json at {}. {:#}",
- path.display(),
- AnyError::from(err),
- ),
- };
-
- let package_json = Rc::new(Self::load_from_string(path, source)?);
- CACHE.with(|cache| {
- cache
- .borrow_mut()
- .insert(package_json.path.clone(), package_json.clone());
- });
- Ok(package_json)
- }
-
- pub fn load_from_string(
- path: PathBuf,
- source: String,
- ) -> Result<PackageJson, AnyError> {
- if source.trim().is_empty() {
- return Ok(PackageJson::empty(path));
- }
-
- let package_json: Value = serde_json::from_str(&source).map_err(|err| {
- anyhow::anyhow!(
- "malformed package.json: {}\n at {}",
- err,
- path.display()
- )
- })?;
- Self::load_from_value(path, package_json)
- }
-
- pub fn load_from_value(
- path: PathBuf,
- package_json: serde_json::Value,
- ) -> Result<PackageJson, AnyError> {
- let imports_val = package_json.get("imports");
- let main_val = package_json.get("main");
- let module_val = package_json.get("module");
- let name_val = package_json.get("name");
- let version_val = package_json.get("version");
- let type_val = package_json.get("type");
- let bin = package_json.get("bin").map(ToOwned::to_owned);
- let exports = package_json.get("exports").and_then(|exports| {
- Some(if is_conditional_exports_main_sugar(exports) {
- let mut map = Map::new();
- map.insert(".".to_string(), exports.to_owned());
- map
- } else {
- exports.as_object()?.to_owned()
- })
- });
-
- let imports = imports_val
- .and_then(|imp| imp.as_object())
- .map(|imp| imp.to_owned());
- let main = main_val.and_then(|s| s.as_str()).map(|s| s.to_string());
- let name = name_val.and_then(|s| s.as_str()).map(|s| s.to_string());
- let version = version_val.and_then(|s| s.as_str()).map(|s| s.to_string());
- let module = module_val.and_then(|s| s.as_str()).map(|s| s.to_string());
-
- let dependencies = package_json.get("dependencies").and_then(|d| {
- if d.is_object() {
- let deps: IndexMap<String, String> =
- serde_json::from_value(d.to_owned()).unwrap();
- Some(deps)
- } else {
- None
- }
- });
- let dev_dependencies = package_json.get("devDependencies").and_then(|d| {
- if d.is_object() {
- let deps: IndexMap<String, String> =
- serde_json::from_value(d.to_owned()).unwrap();
- Some(deps)
- } else {
- None
- }
- });
-
- let scripts: Option<IndexMap<String, String>> = package_json
- .get("scripts")
- .and_then(|d| serde_json::from_value(d.to_owned()).ok());
-
- // Ignore unknown types for forwards compatibility
- let typ = if let Some(t) = type_val {
- if let Some(t) = t.as_str() {
- if t != "module" && t != "commonjs" {
- "none".to_string()
- } else {
- t.to_string()
- }
- } else {
- "none".to_string()
- }
- } else {
- "none".to_string()
- };
-
- // for typescript, it looks for "typings" first, then "types"
- let types = package_json
- .get("typings")
- .or_else(|| package_json.get("types"))
- .and_then(|t| t.as_str().map(|s| s.to_string()));
-
- let package_json = PackageJson {
- exists: true,
- path,
- main,
- name,
- version,
- module,
- typ,
- types,
- exports,
- imports,
- bin,
- dependencies,
- dev_dependencies,
- scripts,
- };
-
- Ok(package_json)
- }
-
- pub fn main(&self, referrer_kind: NodeModuleKind) -> Option<&str> {
- let main = if referrer_kind == NodeModuleKind::Esm && self.typ == "module" {
- self.module.as_ref().or(self.main.as_ref())
- } else {
- self.main.as_ref()
- };
- main.map(|m| m.trim()).filter(|m| !m.is_empty())
- }
+pub struct PackageJsonThreadLocalCache;
- pub fn specifier(&self) -> ModuleSpecifier {
- ModuleSpecifier::from_file_path(&self.path).unwrap()
+impl PackageJsonThreadLocalCache {
+ pub fn clear() {
+ CACHE.with(|cache| cache.borrow_mut().clear());
}
}
-fn is_conditional_exports_main_sugar(exports: &Value) -> bool {
- if exports.is_string() || exports.is_array() {
- return true;
+impl deno_config::package_json::PackageJsonCache
+ for PackageJsonThreadLocalCache
+{
+ fn get(&self, path: &Path) -> Option<PackageJsonRc> {
+ CACHE.with(|cache| cache.borrow().get(path).cloned())
}
- if exports.is_null() || !exports.is_object() {
- return false;
- }
-
- let exports_obj = exports.as_object().unwrap();
- let mut is_conditional_sugar = false;
- let mut i = 0;
- for key in exports_obj.keys() {
- let cur_is_conditional_sugar = key.is_empty() || !key.starts_with('.');
- if i == 0 {
- is_conditional_sugar = cur_is_conditional_sugar;
- i += 1;
- } else if is_conditional_sugar != cur_is_conditional_sugar {
- panic!("\"exports\" cannot contains some keys starting with \'.\' and some not.
- The exports object must either be an object of package subpath keys
- or an object of main entry condition name keys only.")
- }
+ fn set(&self, path: PathBuf, package_json: PackageJsonRc) {
+ CACHE.with(|cache| cache.borrow_mut().insert(path, package_json));
}
-
- is_conditional_sugar
}
-#[cfg(test)]
-mod test {
- use super::*;
-
- #[test]
- fn null_exports_should_not_crash() {
- let package_json = PackageJson::load_from_string(
- PathBuf::from("/package.json"),
- r#"{ "exports": null }"#.to_string(),
- )
- .unwrap();
-
- assert!(package_json.exports.is_none());
+/// Helper to load a package.json file using the thread local cache
+/// in deno_node.
+pub fn load_pkg_json(
+ fs: &dyn deno_fs::FileSystem,
+ path: &Path,
+) -> Result<Option<PackageJsonRc>, PackageJsonLoadError> {
+ let result = PackageJson::load_from_path(
+ path,
+ &DenoConfigFsAdapter::new(fs),
+ Some(&PackageJsonThreadLocalCache),
+ );
+ match result {
+ Ok(pkg_json) => Ok(Some(pkg_json)),
+ Err(PackageJsonLoadError::Io { source, .. })
+ if source.kind() == ErrorKind::NotFound =>
+ {
+ Ok(None)
+ }
+ Err(err) => Err(err),
}
}