2022-01-07 22:09:52 -05:00
|
|
|
// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license.
|
2020-09-21 08:26:41 -04:00
|
|
|
|
2022-10-18 17:09:31 -04:00
|
|
|
use deno_core::anyhow::Context;
|
|
|
|
use deno_core::error::AnyError;
|
2021-10-10 17:26:22 -04:00
|
|
|
use deno_core::parking_lot::Mutex;
|
2022-10-19 17:30:44 -04:00
|
|
|
use deno_core::serde::Deserialize;
|
|
|
|
use deno_core::serde::Serialize;
|
2020-09-21 12:36:37 -04:00
|
|
|
use deno_core::serde_json;
|
2021-10-10 17:26:22 -04:00
|
|
|
use deno_core::ModuleSpecifier;
|
2021-03-26 12:34:25 -04:00
|
|
|
use log::debug;
|
2021-10-10 17:26:22 -04:00
|
|
|
use std::cell::RefCell;
|
2020-07-02 11:54:51 -04:00
|
|
|
use std::collections::BTreeMap;
|
2022-10-18 17:09:31 -04:00
|
|
|
use std::io::Write;
|
2020-10-19 15:19:20 -04:00
|
|
|
use std::path::PathBuf;
|
2021-10-10 17:26:22 -04:00
|
|
|
use std::rc::Rc;
|
|
|
|
use std::sync::Arc;
|
2019-11-03 10:39:27 -05:00
|
|
|
|
2022-10-25 12:20:07 -04:00
|
|
|
use crate::npm::NpmPackageReq;
|
|
|
|
use crate::npm::NpmResolutionPackage;
|
2021-12-07 19:21:04 -05:00
|
|
|
use crate::tools::fmt::format_json;
|
|
|
|
|
2022-10-25 12:20:07 -04:00
|
|
|
#[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()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-10-19 17:30:44 -04:00
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
2022-10-21 15:24:32 -04:00
|
|
|
pub struct LockfileContent {
|
2022-10-19 17:30:44 -04:00
|
|
|
version: String,
|
2022-10-25 12:20:07 -04:00
|
|
|
// Mapping between URLs and their checksums for "http:" and "https:" deps
|
2022-10-19 17:30:44 -04:00
|
|
|
remote: BTreeMap<String, String>,
|
2022-10-25 12:20:07 -04:00
|
|
|
#[serde(skip_serializing_if = "NpmContent::is_empty")]
|
|
|
|
#[serde(default)]
|
|
|
|
pub npm: NpmContent,
|
2022-10-19 17:30:44 -04:00
|
|
|
}
|
|
|
|
|
2020-09-24 18:31:17 -04:00
|
|
|
#[derive(Debug, Clone)]
|
2019-11-03 10:39:27 -05:00
|
|
|
pub struct Lockfile {
|
2022-10-25 12:20:07 -04:00
|
|
|
pub write: bool,
|
|
|
|
pub content: LockfileContent,
|
2020-10-19 15:19:20 -04:00
|
|
|
pub filename: PathBuf,
|
2019-11-03 10:39:27 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Lockfile {
|
2022-10-18 17:09:31 -04:00
|
|
|
pub fn new(filename: PathBuf, write: bool) -> Result<Lockfile, AnyError> {
|
2022-10-19 17:30:44 -04:00
|
|
|
// Writing a lock file always uses the new format.
|
|
|
|
let content = if write {
|
2022-10-21 15:24:32 -04:00
|
|
|
LockfileContent {
|
2022-10-19 17:30:44 -04:00
|
|
|
version: "2".to_string(),
|
|
|
|
remote: BTreeMap::new(),
|
2022-10-25 12:20:07 -04:00
|
|
|
npm: NpmContent::default(),
|
2022-10-21 15:24:32 -04:00
|
|
|
}
|
2020-07-02 11:54:51 -04:00
|
|
|
} else {
|
2022-10-18 17:09:31 -04:00
|
|
|
let s = std::fs::read_to_string(&filename).with_context(|| {
|
|
|
|
format!("Unable to read lockfile: {}", filename.display())
|
|
|
|
})?;
|
2022-10-19 17:30:44 -04:00
|
|
|
let value: serde_json::Value = serde_json::from_str(&s)
|
|
|
|
.context("Unable to parse contents of the lockfile")?;
|
|
|
|
let version = value.get("version").and_then(|v| v.as_str());
|
|
|
|
if version == Some("2") {
|
2022-10-21 15:24:32 -04:00
|
|
|
serde_json::from_value::<LockfileContent>(value)
|
|
|
|
.context("Unable to parse lockfile")?
|
2022-10-19 17:30:44 -04:00
|
|
|
} else {
|
2022-10-21 15:24:32 -04:00
|
|
|
// If there's no version field, we assume that user is using the old
|
|
|
|
// version of the lockfile. We'll migrate it in-place into v2 and it
|
|
|
|
// will be writte in v2 if user uses `--lock-write` flag.
|
|
|
|
let remote: BTreeMap<String, String> =
|
2022-10-19 17:30:44 -04:00
|
|
|
serde_json::from_value(value).context("Unable to parse lockfile")?;
|
2022-10-21 15:24:32 -04:00
|
|
|
LockfileContent {
|
|
|
|
version: "2".to_string(),
|
|
|
|
remote,
|
2022-10-25 12:20:07 -04:00
|
|
|
npm: NpmContent::default(),
|
2022-10-21 15:24:32 -04:00
|
|
|
}
|
2022-10-19 17:30:44 -04:00
|
|
|
}
|
2020-07-02 11:54:51 -04:00
|
|
|
};
|
|
|
|
|
|
|
|
Ok(Lockfile {
|
|
|
|
write,
|
2022-10-19 17:30:44 -04:00
|
|
|
content,
|
2019-11-03 10:39:27 -05:00
|
|
|
filename,
|
2020-07-02 11:54:51 -04:00
|
|
|
})
|
2019-11-03 10:39:27 -05:00
|
|
|
}
|
|
|
|
|
2020-07-02 11:54:51 -04:00
|
|
|
// Synchronize lock file to disk - noop if --lock-write file is not specified.
|
2022-10-18 17:09:31 -04:00
|
|
|
pub fn write(&self) -> Result<(), AnyError> {
|
2020-07-02 11:54:51 -04:00
|
|
|
if !self.write {
|
|
|
|
return Ok(());
|
|
|
|
}
|
2021-12-07 19:21:04 -05:00
|
|
|
|
2022-10-21 15:24:32 -04:00
|
|
|
let json_string = serde_json::to_string(&self.content).unwrap();
|
2022-10-19 17:30:44 -04:00
|
|
|
let format_s = format_json(&json_string, &Default::default())
|
2022-03-29 13:33:00 -04:00
|
|
|
.ok()
|
|
|
|
.flatten()
|
2022-10-19 17:30:44 -04:00
|
|
|
.unwrap_or(json_string);
|
2019-11-03 10:39:27 -05:00
|
|
|
let mut f = std::fs::OpenOptions::new()
|
|
|
|
.write(true)
|
|
|
|
.create(true)
|
|
|
|
.truncate(true)
|
|
|
|
.open(&self.filename)?;
|
2021-12-07 19:21:04 -05:00
|
|
|
f.write_all(format_s.as_bytes())?;
|
2020-10-19 15:19:20 -04:00
|
|
|
debug!("lockfile write {}", self.filename.display());
|
2019-11-03 10:39:27 -05:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2022-10-25 12:20:07 -04:00
|
|
|
// 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 {
|
2020-07-02 11:54:51 -04:00
|
|
|
if self.write {
|
|
|
|
// In case --lock-write is specified check always passes
|
|
|
|
self.insert(specifier, code);
|
|
|
|
true
|
|
|
|
} else {
|
|
|
|
self.check(specifier, code)
|
|
|
|
}
|
2019-11-03 10:39:27 -05:00
|
|
|
}
|
|
|
|
|
2022-10-25 12:20:07 -04:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-07-02 11:54:51 -04:00
|
|
|
/// Checks the given module is included.
|
|
|
|
/// Returns Ok(true) if check passed.
|
|
|
|
fn check(&mut self, specifier: &str, code: &str) -> bool {
|
|
|
|
if specifier.starts_with("file:") {
|
|
|
|
return true;
|
2019-11-03 10:39:27 -05:00
|
|
|
}
|
2022-10-21 15:24:32 -04:00
|
|
|
if let Some(lockfile_checksum) = self.content.remote.get(specifier) {
|
|
|
|
let compiled_checksum = crate::checksum::gen(&[code.as_bytes()]);
|
|
|
|
lockfile_checksum == &compiled_checksum
|
|
|
|
} else {
|
|
|
|
false
|
2020-07-02 11:54:51 -04:00
|
|
|
}
|
2019-11-03 10:39:27 -05:00
|
|
|
}
|
|
|
|
|
2020-07-02 11:54:51 -04:00
|
|
|
fn insert(&mut self, specifier: &str, code: &str) {
|
|
|
|
if specifier.starts_with("file:") {
|
|
|
|
return;
|
2019-11-03 10:39:27 -05:00
|
|
|
}
|
2020-07-02 11:54:51 -04:00
|
|
|
let checksum = crate::checksum::gen(&[code.as_bytes()]);
|
2022-10-21 15:24:32 -04:00
|
|
|
self.content.remote.insert(specifier.to_string(), checksum);
|
2019-11-03 10:39:27 -05:00
|
|
|
}
|
2022-10-25 12:20:07 -04:00
|
|
|
|
|
|
|
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),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
2019-11-03 10:39:27 -05:00
|
|
|
}
|
2020-10-04 19:32:18 -04:00
|
|
|
|
2021-10-10 17:26:22 -04:00
|
|
|
#[derive(Debug)]
|
2022-03-23 09:54:22 -04:00
|
|
|
pub struct Locker(Option<Arc<Mutex<Lockfile>>>);
|
2021-10-10 17:26:22 -04:00
|
|
|
|
|
|
|
impl deno_graph::source::Locker for Locker {
|
|
|
|
fn check_or_insert(
|
|
|
|
&mut self,
|
|
|
|
specifier: &ModuleSpecifier,
|
|
|
|
source: &str,
|
|
|
|
) -> bool {
|
|
|
|
if let Some(lock_file) = &self.0 {
|
|
|
|
let mut lock_file = lock_file.lock();
|
2022-10-25 12:20:07 -04:00
|
|
|
lock_file.check_or_insert_remote(specifier.as_str(), source)
|
2021-10-10 17:26:22 -04:00
|
|
|
} else {
|
|
|
|
true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn get_checksum(&self, content: &str) -> String {
|
|
|
|
crate::checksum::gen(&[content.as_bytes()])
|
|
|
|
}
|
|
|
|
|
|
|
|
fn get_filename(&self) -> Option<String> {
|
|
|
|
let lock_file = self.0.as_ref()?.lock();
|
|
|
|
lock_file.filename.to_str().map(|s| s.to_string())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-03-23 09:54:22 -04:00
|
|
|
pub fn as_maybe_locker(
|
2021-10-10 17:26:22 -04:00
|
|
|
lockfile: Option<Arc<Mutex<Lockfile>>>,
|
2022-10-25 11:55:57 -04:00
|
|
|
) -> Option<Rc<RefCell<dyn deno_graph::source::Locker>>> {
|
2021-10-10 17:26:22 -04:00
|
|
|
lockfile.as_ref().map(|lf| {
|
2022-10-25 11:55:57 -04:00
|
|
|
Rc::new(RefCell::new(Locker(Some(lf.clone()))))
|
|
|
|
as Rc<RefCell<dyn deno_graph::source::Locker>>
|
2021-10-10 17:26:22 -04:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2020-10-04 19:32:18 -04:00
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use super::*;
|
|
|
|
use deno_core::serde_json;
|
|
|
|
use deno_core::serde_json::json;
|
|
|
|
use std::fs::File;
|
|
|
|
use std::io::prelude::*;
|
|
|
|
use std::io::Write;
|
2022-04-01 11:15:37 -04:00
|
|
|
use test_util::TempDir;
|
2020-10-04 19:32:18 -04:00
|
|
|
|
2022-04-01 11:15:37 -04:00
|
|
|
fn setup(temp_dir: &TempDir) -> PathBuf {
|
2020-10-04 19:32:18 -04:00
|
|
|
let file_path = temp_dir.path().join("valid_lockfile.json");
|
|
|
|
let mut file = File::create(file_path).expect("write file fail");
|
|
|
|
|
|
|
|
let value: serde_json::Value = json!({
|
2022-10-19 17:30:44 -04:00
|
|
|
"version": "2",
|
|
|
|
"remote": {
|
|
|
|
"https://deno.land/std@0.71.0/textproto/mod.ts": "3118d7a42c03c242c5a49c2ad91c8396110e14acca1324e7aaefd31a999b71a4",
|
|
|
|
"https://deno.land/std@0.71.0/async/delay.ts": "35957d585a6e3dd87706858fb1d6b551cb278271b03f52c5a2cb70e65e00c26a"
|
2022-10-25 12:20:07 -04:00
|
|
|
},
|
|
|
|
"npm": {
|
|
|
|
"specifiers": {},
|
|
|
|
"packages": {}
|
2022-10-19 17:30:44 -04:00
|
|
|
}
|
2020-10-04 19:32:18 -04:00
|
|
|
});
|
|
|
|
|
|
|
|
file.write_all(value.to_string().as_bytes()).unwrap();
|
|
|
|
|
2022-04-01 11:15:37 -04:00
|
|
|
temp_dir.path().join("valid_lockfile.json")
|
2020-10-04 19:32:18 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn new_nonexistent_lockfile() {
|
2020-10-19 15:19:20 -04:00
|
|
|
let file_path = PathBuf::from("nonexistent_lock_file.json");
|
2020-10-04 19:32:18 -04:00
|
|
|
assert!(Lockfile::new(file_path, false).is_err());
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn new_valid_lockfile() {
|
2022-04-01 11:15:37 -04:00
|
|
|
let temp_dir = TempDir::new();
|
|
|
|
let file_path = setup(&temp_dir);
|
2020-10-04 19:32:18 -04:00
|
|
|
|
|
|
|
let result = Lockfile::new(file_path, false).unwrap();
|
|
|
|
|
2022-10-21 15:24:32 -04:00
|
|
|
let remote = result.content.remote;
|
2022-10-19 17:30:44 -04:00
|
|
|
let keys: Vec<String> = remote.keys().cloned().collect();
|
2020-10-04 19:32:18 -04:00
|
|
|
let expected_keys = vec![
|
|
|
|
String::from("https://deno.land/std@0.71.0/async/delay.ts"),
|
|
|
|
String::from("https://deno.land/std@0.71.0/textproto/mod.ts"),
|
|
|
|
];
|
|
|
|
|
|
|
|
assert_eq!(keys.len(), 2);
|
|
|
|
assert_eq!(keys, expected_keys);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn new_lockfile_from_file_and_insert() {
|
2022-04-01 11:15:37 -04:00
|
|
|
let temp_dir = TempDir::new();
|
|
|
|
let file_path = setup(&temp_dir);
|
2020-10-04 19:32:18 -04:00
|
|
|
|
|
|
|
let mut lockfile = Lockfile::new(file_path, false).unwrap();
|
|
|
|
|
|
|
|
lockfile.insert(
|
|
|
|
"https://deno.land/std@0.71.0/io/util.ts",
|
|
|
|
"Here is some source code",
|
|
|
|
);
|
|
|
|
|
2022-10-21 15:24:32 -04:00
|
|
|
let remote = lockfile.content.remote;
|
2022-10-19 17:30:44 -04:00
|
|
|
let keys: Vec<String> = remote.keys().cloned().collect();
|
2020-10-04 19:32:18 -04:00
|
|
|
let expected_keys = vec![
|
|
|
|
String::from("https://deno.land/std@0.71.0/async/delay.ts"),
|
|
|
|
String::from("https://deno.land/std@0.71.0/io/util.ts"),
|
|
|
|
String::from("https://deno.land/std@0.71.0/textproto/mod.ts"),
|
|
|
|
];
|
|
|
|
assert_eq!(keys.len(), 3);
|
|
|
|
assert_eq!(keys, expected_keys);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn new_lockfile_and_write() {
|
2022-04-01 11:15:37 -04:00
|
|
|
let temp_dir = TempDir::new();
|
|
|
|
let file_path = setup(&temp_dir);
|
2020-10-04 19:32:18 -04:00
|
|
|
|
|
|
|
let mut lockfile = Lockfile::new(file_path, true).unwrap();
|
|
|
|
|
|
|
|
lockfile.insert(
|
|
|
|
"https://deno.land/std@0.71.0/textproto/mod.ts",
|
|
|
|
"Here is some source code",
|
|
|
|
);
|
|
|
|
lockfile.insert(
|
|
|
|
"https://deno.land/std@0.71.0/io/util.ts",
|
|
|
|
"more source code here",
|
|
|
|
);
|
|
|
|
lockfile.insert(
|
|
|
|
"https://deno.land/std@0.71.0/async/delay.ts",
|
|
|
|
"this source is really exciting",
|
|
|
|
);
|
|
|
|
|
|
|
|
lockfile.write().expect("unable to write");
|
|
|
|
|
|
|
|
let file_path_buf = temp_dir.path().join("valid_lockfile.json");
|
|
|
|
let file_path = file_path_buf.to_str().expect("file path fail").to_string();
|
|
|
|
|
|
|
|
// read the file contents back into a string and check
|
|
|
|
let mut checkfile = File::open(file_path).expect("Unable to open the file");
|
|
|
|
let mut contents = String::new();
|
|
|
|
checkfile
|
|
|
|
.read_to_string(&mut contents)
|
|
|
|
.expect("Unable to read the file");
|
|
|
|
|
2020-11-26 07:16:48 -05:00
|
|
|
let contents_json =
|
|
|
|
serde_json::from_str::<serde_json::Value>(&contents).unwrap();
|
2022-10-19 17:30:44 -04:00
|
|
|
let object = contents_json["remote"].as_object().unwrap();
|
2020-11-26 07:16:48 -05:00
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
object
|
|
|
|
.get("https://deno.land/std@0.71.0/textproto/mod.ts")
|
|
|
|
.and_then(|v| v.as_str()),
|
|
|
|
// sha-256 hash of the source 'Here is some source code'
|
|
|
|
Some("fedebba9bb82cce293196f54b21875b649e457f0eaf55556f1e318204947a28f")
|
|
|
|
);
|
|
|
|
|
|
|
|
// confirm that keys are sorted alphabetically
|
|
|
|
let mut keys = object.keys().map(|k| k.as_str());
|
|
|
|
assert_eq!(
|
|
|
|
keys.next(),
|
|
|
|
Some("https://deno.land/std@0.71.0/async/delay.ts")
|
|
|
|
);
|
|
|
|
assert_eq!(keys.next(), Some("https://deno.land/std@0.71.0/io/util.ts"));
|
|
|
|
assert_eq!(
|
|
|
|
keys.next(),
|
|
|
|
Some("https://deno.land/std@0.71.0/textproto/mod.ts")
|
|
|
|
);
|
|
|
|
assert!(keys.next().is_none());
|
2020-10-04 19:32:18 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn check_or_insert_lockfile_false() {
|
2022-04-01 11:15:37 -04:00
|
|
|
let temp_dir = TempDir::new();
|
|
|
|
let file_path = setup(&temp_dir);
|
2020-10-04 19:32:18 -04:00
|
|
|
|
|
|
|
let mut lockfile = Lockfile::new(file_path, false).unwrap();
|
|
|
|
|
|
|
|
lockfile.insert(
|
|
|
|
"https://deno.land/std@0.71.0/textproto/mod.ts",
|
|
|
|
"Here is some source code",
|
|
|
|
);
|
|
|
|
|
2022-10-25 12:20:07 -04:00
|
|
|
let check_true = lockfile.check_or_insert_remote(
|
2020-10-04 19:32:18 -04:00
|
|
|
"https://deno.land/std@0.71.0/textproto/mod.ts",
|
|
|
|
"Here is some source code",
|
|
|
|
);
|
|
|
|
assert!(check_true);
|
|
|
|
|
2022-10-25 12:20:07 -04:00
|
|
|
let check_false = lockfile.check_or_insert_remote(
|
2020-10-04 19:32:18 -04:00
|
|
|
"https://deno.land/std@0.71.0/textproto/mod.ts",
|
|
|
|
"This is new Source code",
|
|
|
|
);
|
|
|
|
assert!(!check_false);
|
|
|
|
}
|
|
|
|
}
|