summaryrefslogtreecommitdiff
path: root/cli/lockfile.rs
diff options
context:
space:
mode:
Diffstat (limited to 'cli/lockfile.rs')
-rw-r--r--cli/lockfile.rs148
1 files changed, 139 insertions, 9 deletions
diff --git a/cli/lockfile.rs b/cli/lockfile.rs
index b572b9d93..53a3c1286 100644
--- a/cli/lockfile.rs
+++ b/cli/lockfile.rs
@@ -1,7 +1,5 @@
// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license.
-#![allow(dead_code)]
-
use deno_core::anyhow::Context;
use deno_core::error::AnyError;
use deno_core::parking_lot::Mutex;
@@ -17,19 +15,68 @@ use std::path::PathBuf;
use std::rc::Rc;
use std::sync::Arc;
+use crate::npm::NpmPackageReq;
+use crate::npm::NpmResolutionPackage;
use crate::tools::fmt::format_json;
+#[derive(Debug)]
+pub struct LockfileError(String);
+
+impl std::fmt::Display for LockfileError {
+ fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+ f.write_str(&self.0)
+ }
+}
+
+impl std::error::Error for LockfileError {}
+
+#[derive(Debug, Clone, Serialize, Deserialize)]
+pub struct NpmPackageInfo {
+ pub integrity: String,
+ pub dependencies: BTreeMap<String, String>,
+}
+
+#[derive(Clone, Debug, Default, Serialize, Deserialize)]
+pub struct NpmContent {
+ /// Mapping between requests for npm packages and resolved specifiers, eg.
+ /// {
+ /// "chalk": "chalk@5.0.0"
+ /// "react@17": "react@17.0.1"
+ /// "foo@latest": "foo@1.0.0"
+ /// }
+ pub specifiers: BTreeMap<String, String>,
+ /// Mapping between resolved npm specifiers and their associated info, eg.
+ /// {
+ /// "chalk@5.0.0": {
+ /// "integrity": "sha512-...",
+ /// "dependencies": {
+ /// "ansi-styles": "ansi-styles@4.1.0",
+ /// }
+ /// }
+ /// }
+ pub packages: BTreeMap<String, NpmPackageInfo>,
+}
+
+impl NpmContent {
+ fn is_empty(&self) -> bool {
+ self.specifiers.is_empty() && self.packages.is_empty()
+ }
+}
+
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LockfileContent {
version: String,
- // Mapping between URLs and their checksums
+ // Mapping between URLs and their checksums for "http:" and "https:" deps
remote: BTreeMap<String, String>,
+ #[serde(skip_serializing_if = "NpmContent::is_empty")]
+ #[serde(default)]
+ pub npm: NpmContent,
}
#[derive(Debug, Clone)]
pub struct Lockfile {
- write: bool,
- content: LockfileContent,
+ pub write: bool,
+ pub content: LockfileContent,
pub filename: PathBuf,
}
@@ -40,6 +87,7 @@ impl Lockfile {
LockfileContent {
version: "2".to_string(),
remote: BTreeMap::new(),
+ npm: NpmContent::default(),
}
} else {
let s = std::fs::read_to_string(&filename).with_context(|| {
@@ -60,6 +108,7 @@ impl Lockfile {
LockfileContent {
version: "2".to_string(),
remote,
+ npm: NpmContent::default(),
}
}
};
@@ -92,7 +141,13 @@ impl Lockfile {
Ok(())
}
- pub fn check_or_insert(&mut self, specifier: &str, code: &str) -> bool {
+ // TODO(bartlomieju): this function should return an error instead of a bool,
+ // but it requires changes to `deno_graph`'s `Locker`.
+ pub fn check_or_insert_remote(
+ &mut self,
+ specifier: &str,
+ code: &str,
+ ) -> bool {
if self.write {
// In case --lock-write is specified check always passes
self.insert(specifier, code);
@@ -102,6 +157,19 @@ impl Lockfile {
}
}
+ pub fn check_or_insert_npm_package(
+ &mut self,
+ package: &NpmResolutionPackage,
+ ) -> Result<(), LockfileError> {
+ if self.write {
+ // In case --lock-write is specified check always passes
+ self.insert_npm_package(package);
+ Ok(())
+ } else {
+ self.check_npm_package(package)
+ }
+ }
+
/// Checks the given module is included.
/// Returns Ok(true) if check passed.
fn check(&mut self, specifier: &str, code: &str) -> bool {
@@ -123,6 +191,64 @@ impl Lockfile {
let checksum = crate::checksum::gen(&[code.as_bytes()]);
self.content.remote.insert(specifier.to_string(), checksum);
}
+
+ fn check_npm_package(
+ &mut self,
+ package: &NpmResolutionPackage,
+ ) -> Result<(), LockfileError> {
+ let specifier = package.id.serialize_for_lock_file();
+ if let Some(package_info) = self.content.npm.packages.get(&specifier) {
+ let integrity = package
+ .dist
+ .integrity
+ .as_ref()
+ .unwrap_or(&package.dist.shasum);
+ if &package_info.integrity != integrity {
+ return Err(LockfileError(format!(
+ "Integrity check failed for npm package: \"{}\".
+ Cache has \"{}\" and lockfile has \"{}\".
+ Use \"--lock-write\" flag to update the lockfile.",
+ package.id, integrity, package_info.integrity
+ )));
+ }
+ }
+
+ Ok(())
+ }
+
+ fn insert_npm_package(&mut self, package: &NpmResolutionPackage) {
+ let dependencies = package
+ .dependencies
+ .iter()
+ .map(|(name, id)| (name.to_string(), id.serialize_for_lock_file()))
+ .collect::<BTreeMap<String, String>>();
+
+ let integrity = package
+ .dist
+ .integrity
+ .as_ref()
+ .unwrap_or(&package.dist.shasum);
+ self.content.npm.packages.insert(
+ package.id.serialize_for_lock_file(),
+ NpmPackageInfo {
+ integrity: integrity.to_string(),
+ dependencies,
+ },
+ );
+ }
+
+ pub fn insert_npm_specifier(
+ &mut self,
+ package_req: &NpmPackageReq,
+ version: String,
+ ) {
+ if self.write {
+ self.content.npm.specifiers.insert(
+ package_req.to_string(),
+ format!("{}@{}", package_req.name, version),
+ );
+ }
+ }
}
#[derive(Debug)]
@@ -136,7 +262,7 @@ impl deno_graph::source::Locker for Locker {
) -> bool {
if let Some(lock_file) = &self.0 {
let mut lock_file = lock_file.lock();
- lock_file.check_or_insert(specifier.as_str(), source)
+ lock_file.check_or_insert_remote(specifier.as_str(), source)
} else {
true
}
@@ -180,6 +306,10 @@ mod tests {
"remote": {
"https://deno.land/std@0.71.0/textproto/mod.ts": "3118d7a42c03c242c5a49c2ad91c8396110e14acca1324e7aaefd31a999b71a4",
"https://deno.land/std@0.71.0/async/delay.ts": "35957d585a6e3dd87706858fb1d6b551cb278271b03f52c5a2cb70e65e00c26a"
+ },
+ "npm": {
+ "specifiers": {},
+ "packages": {}
}
});
@@ -305,13 +435,13 @@ mod tests {
"Here is some source code",
);
- let check_true = lockfile.check_or_insert(
+ let check_true = lockfile.check_or_insert_remote(
"https://deno.land/std@0.71.0/textproto/mod.ts",
"Here is some source code",
);
assert!(check_true);
- let check_false = lockfile.check_or_insert(
+ let check_false = lockfile.check_or_insert_remote(
"https://deno.land/std@0.71.0/textproto/mod.ts",
"This is new Source code",
);