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