deno_package_json/
lib.rs

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