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::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  /// "workspace:~"
65  Tilde,
66
67  /// "workspace:^"
68  Caret,
69
70  /// "workspace:x.y.z", "workspace:*", "workspace:^x.y.z"
71  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    /// Gets the name and raw version constraint for a registry info or
88    /// package.json dependency entry taking into account npm package aliases.
89    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 empty, then the name was scoped and there's no version
96          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  /// Gets a package.json dependency entry by alias.
177  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    // Ignore unknown types for forwards compatibility
435    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    // for typescript, it looks for "typings" first, then "types"
450    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  /// Resolve the package.json's dependencies.
499  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  // https://github.com/denoland/deno/issues/26031
855  #[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}