1#![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 Tilde,
62
63 Caret,
65
66 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 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 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 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 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 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 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 #[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}