deno_package_json/
lib.rs

1// Copyright 2018-2024 the Deno authors. MIT license.
2
3#![deny(clippy::print_stderr)]
4#![deny(clippy::print_stdout)]
5#![deny(clippy::unused_async)]
6#![deny(clippy::unnecessary_wraps)]
7
8use std::path::Path;
9use std::path::PathBuf;
10
11use boxed_error::Boxed;
12use deno_error::JsError;
13use deno_semver::npm::NpmVersionReqParseError;
14use deno_semver::package::PackageReq;
15use deno_semver::StackString;
16use deno_semver::VersionReq;
17use indexmap::IndexMap;
18use serde::Serialize;
19use serde_json::Map;
20use serde_json::Value;
21use sys_traits::FsRead;
22use thiserror::Error;
23use url::Url;
24
25mod sync;
26
27#[allow(clippy::disallowed_types)]
28pub type PackageJsonRc = crate::sync::MaybeArc<PackageJson>;
29#[allow(clippy::disallowed_types)]
30pub type PackageJsonDepsRc = crate::sync::MaybeArc<PackageJsonDeps>;
31#[allow(clippy::disallowed_types)]
32type PackageJsonDepsRcCell = crate::sync::MaybeOnceLock<PackageJsonDepsRc>;
33
34pub trait PackageJsonCache {
35  fn get(&self, path: &Path) -> Option<PackageJsonRc>;
36  fn set(&self, path: PathBuf, package_json: PackageJsonRc);
37}
38
39#[derive(Debug, Clone, JsError, PartialEq, Eq, Boxed)]
40pub struct PackageJsonDepValueParseError(
41  pub Box<PackageJsonDepValueParseErrorKind>,
42);
43
44#[derive(Debug, Error, Clone, JsError, PartialEq, Eq)]
45pub enum PackageJsonDepValueParseErrorKind {
46  #[class(inherit)]
47  #[error(transparent)]
48  VersionReq(#[from] NpmVersionReqParseError),
49  #[class(type)]
50  #[error("Not implemented scheme '{scheme}'")]
51  Unsupported { scheme: String },
52}
53
54#[derive(Debug, Clone, PartialEq, Eq, Hash)]
55pub enum PackageJsonDepWorkspaceReq {
56  /// "workspace:~"
57  Tilde,
58
59  /// "workspace:^"
60  Caret,
61
62  /// "workspace:x.y.z", "workspace:*", "workspace:^x.y.z"
63  VersionReq(VersionReq),
64}
65
66#[derive(Debug, Clone, PartialEq, Eq, Hash)]
67pub enum PackageJsonDepValue {
68  File(String),
69  Req(PackageReq),
70  Workspace(PackageJsonDepWorkspaceReq),
71}
72
73impl PackageJsonDepValue {
74  pub fn parse(
75    key: &str,
76    value: &str,
77  ) -> Result<Self, PackageJsonDepValueParseError> {
78    /// Gets the name and raw version constraint for a registry info or
79    /// package.json dependency entry taking into account npm package aliases.
80    fn parse_dep_entry_name_and_raw_version<'a>(
81      key: &'a str,
82      value: &'a str,
83    ) -> (&'a str, &'a str) {
84      if let Some(package_and_version) = value.strip_prefix("npm:") {
85        if let Some((name, version)) = package_and_version.rsplit_once('@') {
86          // if empty, then the name was scoped and there's no version
87          if name.is_empty() {
88            (package_and_version, "*")
89          } else {
90            (name, version)
91          }
92        } else {
93          (package_and_version, "*")
94        }
95      } else {
96        (key, value)
97      }
98    }
99
100    if let Some(workspace_key) = value.strip_prefix("workspace:") {
101      let workspace_req = match workspace_key {
102        "~" => PackageJsonDepWorkspaceReq::Tilde,
103        "^" => PackageJsonDepWorkspaceReq::Caret,
104        _ => PackageJsonDepWorkspaceReq::VersionReq(
105          VersionReq::parse_from_npm(workspace_key)?,
106        ),
107      };
108      return Ok(Self::Workspace(workspace_req));
109    }
110    if value.starts_with("git:")
111      || value.starts_with("http:")
112      || value.starts_with("https:")
113    {
114      return Err(
115        PackageJsonDepValueParseErrorKind::Unsupported {
116          scheme: value.split(':').next().unwrap().to_string(),
117        }
118        .into_box(),
119      );
120    }
121    if let Some(path) = value.strip_prefix("file:") {
122      return Ok(Self::File(path.to_string()));
123    }
124    let (name, version_req) = parse_dep_entry_name_and_raw_version(key, value);
125    let result = VersionReq::parse_from_npm(version_req);
126    match result {
127      Ok(version_req) => Ok(Self::Req(PackageReq {
128        name: name.into(),
129        version_req,
130      })),
131      Err(err) => {
132        Err(PackageJsonDepValueParseErrorKind::VersionReq(err).into_box())
133      }
134    }
135  }
136}
137
138pub type PackageJsonDepsMap = IndexMap<
139  StackString,
140  Result<PackageJsonDepValue, PackageJsonDepValueParseError>,
141>;
142
143#[derive(Debug, Clone)]
144pub struct PackageJsonDeps {
145  pub dependencies: PackageJsonDepsMap,
146  pub dev_dependencies: PackageJsonDepsMap,
147}
148
149impl PackageJsonDeps {
150  /// Gets a package.json dependency entry by alias.
151  pub fn get(
152    &self,
153    alias: &str,
154  ) -> Option<&Result<PackageJsonDepValue, PackageJsonDepValueParseError>> {
155    self
156      .dependencies
157      .get(alias)
158      .or_else(|| self.dev_dependencies.get(alias))
159  }
160}
161
162#[derive(Debug, Error, JsError)]
163pub enum PackageJsonLoadError {
164  #[class(inherit)]
165  #[error("Failed reading '{}'.", .path.display())]
166  Io {
167    path: PathBuf,
168    #[source]
169    #[inherit]
170    source: std::io::Error,
171  },
172  #[class(inherit)]
173  #[error("Malformed package.json '{}'.", .path.display())]
174  Deserialize {
175    path: PathBuf,
176    #[source]
177    #[inherit]
178    source: serde_json::Error,
179  },
180  #[error("\"exports\" cannot contains some keys starting with '.' and some not.\nThe exports object must either be an object of package subpath keys\nor an object of main entry condition name keys only.")]
181  #[class(type)]
182  InvalidExports,
183}
184
185#[derive(Clone, Copy, Debug, PartialEq, Eq)]
186pub enum NodeModuleKind {
187  Esm,
188  Cjs,
189}
190
191#[derive(Clone, Debug, Serialize)]
192#[serde(rename_all = "camelCase")]
193pub struct PackageJson {
194  pub exports: Option<Map<String, Value>>,
195  pub imports: Option<Map<String, Value>>,
196  pub bin: Option<Value>,
197  main: Option<String>,   // use .main(...)
198  module: Option<String>, // use .main(...)
199  pub name: Option<String>,
200  pub version: Option<String>,
201  #[serde(skip)]
202  pub path: PathBuf,
203  #[serde(rename = "type")]
204  pub typ: String,
205  pub types: Option<String>,
206  pub types_versions: Option<Map<String, Value>>,
207  pub dependencies: Option<IndexMap<String, String>>,
208  pub dev_dependencies: Option<IndexMap<String, String>>,
209  pub peer_dependencies: Option<IndexMap<String, String>>,
210  pub peer_dependencies_meta: Option<Value>,
211  pub optional_dependencies: Option<IndexMap<String, String>>,
212  pub scripts: Option<IndexMap<String, String>>,
213  pub workspaces: Option<Vec<String>>,
214  pub os: Option<Vec<String>>,
215  pub cpu: Option<Vec<String>>,
216  #[serde(skip_serializing)]
217  resolved_deps: PackageJsonDepsRcCell,
218}
219
220impl PackageJson {
221  pub fn load_from_path(
222    sys: &impl FsRead,
223    maybe_cache: Option<&dyn PackageJsonCache>,
224    path: &Path,
225  ) -> Result<PackageJsonRc, PackageJsonLoadError> {
226    if let Some(item) = maybe_cache.and_then(|c| c.get(path)) {
227      Ok(item)
228    } else {
229      match sys.fs_read_to_string_lossy(path) {
230        Ok(file_text) => {
231          let pkg_json =
232            PackageJson::load_from_string(path.to_path_buf(), &file_text)?;
233          let pkg_json = crate::sync::new_rc(pkg_json);
234          if let Some(cache) = maybe_cache {
235            cache.set(path.to_path_buf(), pkg_json.clone());
236          }
237          Ok(pkg_json)
238        }
239        Err(err) => Err(PackageJsonLoadError::Io {
240          path: path.to_path_buf(),
241          source: err,
242        }),
243      }
244    }
245  }
246
247  pub fn load_from_string(
248    path: PathBuf,
249    source: &str,
250  ) -> Result<PackageJson, PackageJsonLoadError> {
251    if source.trim().is_empty() {
252      return Ok(PackageJson {
253        path,
254        main: None,
255        name: None,
256        version: None,
257        module: None,
258        typ: "none".to_string(),
259        types: None,
260        types_versions: None,
261        exports: None,
262        imports: None,
263        bin: None,
264        dependencies: None,
265        dev_dependencies: None,
266        peer_dependencies: None,
267        peer_dependencies_meta: None,
268        optional_dependencies: None,
269        scripts: None,
270        workspaces: None,
271        os: None,
272        cpu: None,
273        resolved_deps: Default::default(),
274      });
275    }
276
277    let package_json: Value = serde_json::from_str(source).map_err(|err| {
278      PackageJsonLoadError::Deserialize {
279        path: path.clone(),
280        source: err,
281      }
282    })?;
283    Self::load_from_value(path, package_json)
284  }
285
286  pub fn load_from_value(
287    path: PathBuf,
288    package_json: serde_json::Value,
289  ) -> Result<PackageJson, PackageJsonLoadError> {
290    fn parse_string_map(
291      value: serde_json::Value,
292    ) -> Option<IndexMap<String, String>> {
293      if let Value::Object(map) = value {
294        let mut result = IndexMap::with_capacity(map.len());
295        for (k, v) in map {
296          if let Some(v) = map_string(v) {
297            result.insert(k, v);
298          }
299        }
300        Some(result)
301      } else {
302        None
303      }
304    }
305
306    fn map_object(value: serde_json::Value) -> Option<Map<String, Value>> {
307      match value {
308        Value::Object(v) => Some(v),
309        _ => None,
310      }
311    }
312
313    fn map_string(value: serde_json::Value) -> Option<String> {
314      match value {
315        Value::String(v) => Some(v),
316        Value::Number(v) => Some(v.to_string()),
317        _ => None,
318      }
319    }
320
321    fn map_array(value: serde_json::Value) -> Option<Vec<Value>> {
322      match value {
323        Value::Array(v) => Some(v),
324        _ => None,
325      }
326    }
327
328    fn parse_string_array(value: serde_json::Value) -> Option<Vec<String>> {
329      let value = map_array(value)?;
330      let mut result = Vec::with_capacity(value.len());
331      for v in value {
332        if let Some(v) = map_string(v) {
333          result.push(v);
334        }
335      }
336      Some(result)
337    }
338
339    let mut package_json = match package_json {
340      Value::Object(o) => o,
341      _ => Default::default(),
342    };
343    let imports_val = package_json.remove("imports");
344    let main_val = package_json.remove("main");
345    let module_val = package_json.remove("module");
346    let name_val = package_json.remove("name");
347    let version_val = package_json.remove("version");
348    let type_val = package_json.remove("type");
349    let bin = package_json.remove("bin");
350    let exports = package_json
351      .remove("exports")
352      .map(|exports| {
353        if is_conditional_exports_main_sugar(&exports)? {
354          let mut map = Map::new();
355          map.insert(".".to_string(), exports.to_owned());
356          Ok::<_, PackageJsonLoadError>(Some(map))
357        } else {
358          Ok(exports.as_object().map(|o| o.to_owned()))
359        }
360      })
361      .transpose()?
362      .flatten();
363
364    let imports = imports_val.and_then(map_object);
365    let main = main_val.and_then(map_string);
366    let name = name_val.and_then(map_string);
367    let version = version_val.and_then(map_string);
368    let module = module_val.and_then(map_string);
369
370    let dependencies = package_json
371      .remove("dependencies")
372      .and_then(parse_string_map);
373    let dev_dependencies = package_json
374      .remove("devDependencies")
375      .and_then(parse_string_map);
376    let peer_dependencies = package_json
377      .remove("peerDependencies")
378      .and_then(parse_string_map);
379    let peer_dependencies_meta = package_json.remove("peerDependenciesMeta");
380    let optional_dependencies = package_json
381      .remove("optionalDependencies")
382      .and_then(parse_string_map);
383
384    let scripts: Option<IndexMap<String, String>> =
385      package_json.remove("scripts").and_then(parse_string_map);
386
387    // Ignore unknown types for forwards compatibility
388    let typ = if let Some(t) = type_val {
389      if let Some(t) = t.as_str() {
390        if t != "module" && t != "commonjs" {
391          "none".to_string()
392        } else {
393          t.to_string()
394        }
395      } else {
396        "none".to_string()
397      }
398    } else {
399      "none".to_string()
400    };
401
402    // for typescript, it looks for "typings" first, then "types"
403    let types = package_json
404      .remove("typings")
405      .or_else(|| package_json.remove("types"))
406      .and_then(map_string);
407    let types_versions = package_json
408      .remove("typesVersions")
409      .and_then(|exports| exports.as_object().map(|o| o.to_owned()));
410    let workspaces = package_json
411      .remove("workspaces")
412      .and_then(parse_string_array);
413    let os = package_json.remove("os").and_then(parse_string_array);
414    let cpu = package_json.remove("cpu").and_then(parse_string_array);
415
416    Ok(PackageJson {
417      path,
418      main,
419      name,
420      version,
421      module,
422      typ,
423      types,
424      types_versions,
425      exports,
426      imports,
427      bin,
428      dependencies,
429      dev_dependencies,
430      peer_dependencies,
431      peer_dependencies_meta,
432      optional_dependencies,
433      scripts,
434      workspaces,
435      os,
436      cpu,
437      resolved_deps: Default::default(),
438    })
439  }
440
441  pub fn specifier(&self) -> Url {
442    deno_path_util::url_from_file_path(&self.path).unwrap()
443  }
444
445  pub fn dir_path(&self) -> &Path {
446    self.path.parent().unwrap()
447  }
448
449  pub fn main(&self, referrer_kind: NodeModuleKind) -> Option<&str> {
450    let main = if referrer_kind == NodeModuleKind::Esm && self.typ == "module" {
451      self.module.as_ref().or(self.main.as_ref())
452    } else {
453      self.main.as_ref()
454    };
455    main.map(|m| m.trim()).filter(|m| !m.is_empty())
456  }
457
458  /// Resolve the package.json's dependencies.
459  pub fn resolve_local_package_json_deps(&self) -> &PackageJsonDepsRc {
460    fn get_map(deps: Option<&IndexMap<String, String>>) -> PackageJsonDepsMap {
461      let Some(deps) = deps else {
462        return Default::default();
463      };
464      let mut result = IndexMap::with_capacity(deps.len());
465      for (key, value) in deps {
466        result
467          .entry(StackString::from(key.as_str()))
468          .or_insert_with(|| PackageJsonDepValue::parse(key, value));
469      }
470      result
471    }
472
473    self.resolved_deps.get_or_init(|| {
474      PackageJsonDepsRc::new(PackageJsonDeps {
475        dependencies: get_map(self.dependencies.as_ref()),
476        dev_dependencies: get_map(self.dev_dependencies.as_ref()),
477      })
478    })
479  }
480}
481
482fn is_conditional_exports_main_sugar(
483  exports: &Value,
484) -> Result<bool, PackageJsonLoadError> {
485  if exports.is_string() || exports.is_array() {
486    return Ok(true);
487  }
488
489  if exports.is_null() || !exports.is_object() {
490    return Ok(false);
491  }
492
493  let exports_obj = exports.as_object().unwrap();
494  let mut is_conditional_sugar = false;
495  let mut i = 0;
496  for key in exports_obj.keys() {
497    let cur_is_conditional_sugar = key.is_empty() || !key.starts_with('.');
498    if i == 0 {
499      is_conditional_sugar = cur_is_conditional_sugar;
500      i += 1;
501    } else if is_conditional_sugar != cur_is_conditional_sugar {
502      return Err(PackageJsonLoadError::InvalidExports);
503    }
504  }
505
506  Ok(is_conditional_sugar)
507}
508
509#[cfg(test)]
510mod test {
511  use super::*;
512  use pretty_assertions::assert_eq;
513  use std::error::Error;
514  use std::path::PathBuf;
515
516  #[test]
517  fn null_exports_should_not_crash() {
518    let package_json = PackageJson::load_from_string(
519      PathBuf::from("/package.json"),
520      r#"{ "exports": null }"#,
521    )
522    .unwrap();
523
524    assert!(package_json.exports.is_none());
525  }
526
527  fn get_local_package_json_version_reqs_for_tests(
528    package_json: &PackageJson,
529  ) -> IndexMap<
530    String,
531    Result<PackageJsonDepValue, PackageJsonDepValueParseErrorKind>,
532  > {
533    let deps = package_json.resolve_local_package_json_deps();
534    deps
535      .dependencies
536      .clone()
537      .into_iter()
538      .chain(deps.dev_dependencies.clone())
539      .map(|(k, v)| {
540        (
541          k.to_string(),
542          match v {
543            Ok(v) => Ok(v),
544            Err(err) => Err(err.into_kind()),
545          },
546        )
547      })
548      .collect::<IndexMap<_, _>>()
549  }
550
551  #[test]
552  fn test_get_local_package_json_version_reqs() {
553    let mut package_json =
554      PackageJson::load_from_string(PathBuf::from("/package.json"), "{}")
555        .unwrap();
556    package_json.dependencies = Some(IndexMap::from([
557      ("test".to_string(), "^1.2".to_string()),
558      ("other".to_string(), "npm:package@~1.3".to_string()),
559    ]));
560    package_json.dev_dependencies = Some(IndexMap::from([
561      ("package_b".to_string(), "~2.2".to_string()),
562      ("other".to_string(), "^3.2".to_string()),
563    ]));
564    let deps = package_json.resolve_local_package_json_deps();
565    assert_eq!(
566      deps
567        .dependencies
568        .clone()
569        .into_iter()
570        .map(|d| (d.0, d.1.unwrap()))
571        .collect::<Vec<_>>(),
572      Vec::from([
573        (
574          "test".into(),
575          PackageJsonDepValue::Req(PackageReq::from_str("test@^1.2").unwrap())
576        ),
577        (
578          "other".into(),
579          PackageJsonDepValue::Req(
580            PackageReq::from_str("package@~1.3").unwrap()
581          )
582        ),
583      ])
584    );
585    assert_eq!(
586      deps
587        .dev_dependencies
588        .clone()
589        .into_iter()
590        .map(|d| (d.0, d.1.unwrap()))
591        .collect::<Vec<_>>(),
592      Vec::from([
593        (
594          "package_b".into(),
595          PackageJsonDepValue::Req(
596            PackageReq::from_str("package_b@~2.2").unwrap()
597          )
598        ),
599        (
600          "other".into(),
601          PackageJsonDepValue::Req(PackageReq::from_str("other@^3.2").unwrap())
602        ),
603      ])
604    );
605  }
606
607  #[test]
608  fn test_get_local_package_json_version_reqs_errors_non_npm_specifier() {
609    let mut package_json =
610      PackageJson::load_from_string(PathBuf::from("/package.json"), "{}")
611        .unwrap();
612    package_json.dependencies = Some(IndexMap::from([(
613      "test".to_string(),
614      "%*(#$%()".to_string(),
615    )]));
616    let map = get_local_package_json_version_reqs_for_tests(&package_json);
617    assert_eq!(map.len(), 1);
618    let err = map.get("test").unwrap().as_ref().unwrap_err();
619    assert_eq!(format!("{}", err), "Invalid version requirement");
620    assert_eq!(
621      format!("{}", err.source().unwrap()),
622      concat!("Unexpected character.\n", "  %*(#$%()\n", "  ~")
623    );
624  }
625
626  #[test]
627  fn test_get_local_package_json_version_reqs_range() {
628    let mut package_json =
629      PackageJson::load_from_string(PathBuf::from("/package.json"), "{}")
630        .unwrap();
631    package_json.dependencies = Some(IndexMap::from([(
632      "test".to_string(),
633      "1.x - 1.3".to_string(),
634    )]));
635    let map = get_local_package_json_version_reqs_for_tests(&package_json);
636    assert_eq!(
637      map,
638      IndexMap::from([(
639        "test".to_string(),
640        Ok(PackageJsonDepValue::Req(PackageReq {
641          name: "test".into(),
642          version_req: VersionReq::parse_from_npm("1.x - 1.3").unwrap()
643        }))
644      )])
645    );
646  }
647
648  #[test]
649  fn test_get_local_package_json_version_reqs_skips_certain_specifiers() {
650    let mut package_json =
651      PackageJson::load_from_string(PathBuf::from("/package.json"), "{}")
652        .unwrap();
653    package_json.dependencies = Some(IndexMap::from([
654      ("test".to_string(), "1".to_string()),
655      (
656        "work-test-version-req".to_string(),
657        "workspace:1.1.1".to_string(),
658      ),
659      ("work-test-star".to_string(), "workspace:*".to_string()),
660      ("work-test-tilde".to_string(), "workspace:~".to_string()),
661      ("work-test-caret".to_string(), "workspace:^".to_string()),
662      ("file-test".to_string(), "file:something".to_string()),
663      ("git-test".to_string(), "git:something".to_string()),
664      ("http-test".to_string(), "http://something".to_string()),
665      ("https-test".to_string(), "https://something".to_string()),
666    ]));
667    let result = get_local_package_json_version_reqs_for_tests(&package_json);
668    assert_eq!(
669      result,
670      IndexMap::from([
671        (
672          "test".to_string(),
673          Ok(PackageJsonDepValue::Req(
674            PackageReq::from_str("test@1").unwrap()
675          ))
676        ),
677        (
678          "work-test-star".to_string(),
679          Ok(PackageJsonDepValue::Workspace(
680            PackageJsonDepWorkspaceReq::VersionReq(
681              VersionReq::parse_from_npm("*").unwrap()
682            )
683          ))
684        ),
685        (
686          "work-test-version-req".to_string(),
687          Ok(PackageJsonDepValue::Workspace(
688            PackageJsonDepWorkspaceReq::VersionReq(
689              VersionReq::parse_from_npm("1.1.1").unwrap()
690            )
691          ))
692        ),
693        (
694          "work-test-tilde".to_string(),
695          Ok(PackageJsonDepValue::Workspace(
696            PackageJsonDepWorkspaceReq::Tilde
697          ))
698        ),
699        (
700          "work-test-caret".to_string(),
701          Ok(PackageJsonDepValue::Workspace(
702            PackageJsonDepWorkspaceReq::Caret
703          ))
704        ),
705        (
706          "file-test".to_string(),
707          Ok(PackageJsonDepValue::File("something".to_string())),
708        ),
709        (
710          "git-test".to_string(),
711          Err(PackageJsonDepValueParseErrorKind::Unsupported {
712            scheme: "git".to_string()
713          }),
714        ),
715        (
716          "http-test".to_string(),
717          Err(PackageJsonDepValueParseErrorKind::Unsupported {
718            scheme: "http".to_string()
719          }),
720        ),
721        (
722          "https-test".to_string(),
723          Err(PackageJsonDepValueParseErrorKind::Unsupported {
724            scheme: "https".to_string()
725          }),
726        ),
727      ])
728    );
729  }
730
731  #[test]
732  fn test_deserialize_serialize() {
733    let json_value = serde_json::json!({
734      "name": "test",
735      "version": "1",
736      "exports": {
737        ".": "./main.js",
738      },
739      "bin": "./main.js",
740      "types": "./types.d.ts",
741      "typesVersions": {
742        "<4.0": { "index.d.ts": ["index.v3.d.ts"] }
743      },
744      "imports": {
745        "#test": "./main.js",
746      },
747      "main": "./main.js",
748      "module": "./module.js",
749      "type": "module",
750      "dependencies": {
751        "name": "1.2",
752      },
753      "devDependencies": {
754        "name": "1.2",
755      },
756      "scripts": {
757        "test": "echo \"Error: no test specified\" && exit 1",
758      },
759      "workspaces": ["asdf", "asdf2"],
760      "cpu": ["x86_64"],
761      "os": ["win32"],
762      "optionalDependencies": {
763        "optional": "1.1"
764      },
765      "peerDependencies": {
766        "peer": "1.0"
767      },
768      "peerDependenciesMeta": {
769        "peer": {
770          "optional": true
771        }
772      },
773    });
774    let package_json = PackageJson::load_from_value(
775      PathBuf::from("/package.json"),
776      json_value.clone(),
777    )
778    .unwrap();
779    let serialized_value = serde_json::to_value(&package_json).unwrap();
780    assert_eq!(serialized_value, json_value);
781  }
782
783  // https://github.com/denoland/deno/issues/26031
784  #[test]
785  fn test_exports_error() {
786    let json_value = serde_json::json!({
787      "name": "test",
788      "version": "1",
789      "exports": { ".": "./a", "a": "./a" },
790    });
791    assert!(matches!(
792      PackageJson::load_from_value(
793        PathBuf::from("/package.json"),
794        json_value.clone(),
795      ),
796      Err(PackageJsonLoadError::InvalidExports)
797    ));
798  }
799}