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