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::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  /// "workspace:~"
79  Tilde,
80
81  /// "workspace:^"
82  Caret,
83
84  /// "workspace:x.y.z", "workspace:*", "workspace:^x.y.z"
85  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    /// Gets the name and raw version constraint for a registry info or
102    /// package.json dependency entry taking into account npm package aliases.
103    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 empty, then the name was scoped and there's no version
110          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  /// Gets a package.json dependency entry by alias.
191  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    // Ignore unknown types for forwards compatibility
449    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    // for typescript, it looks for "typings" first, then "types"
464    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  /// Resolve the package.json's dependencies.
513  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  // https://github.com/denoland/deno/issues/26031
916  #[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}