use super::{BloatDir, EnforcePolicy, PackageManager, dir_size, enforce_two_tier, run_command};
use anyhow::Result;
use std::path::Path;
pub struct Npm;
impl PackageManager for Npm {
fn name(&self) -> &'static str {
"npm"
}
fn detect(&self, project_dir: &Path) -> bool {
project_dir.join("package-lock.json").exists()
}
fn bloat_dirs(&self, project_dir: &Path) -> Vec<BloatDir> {
let node_modules = project_dir.join("node_modules");
if node_modules.exists() {
let size = dir_size(&node_modules);
vec![BloatDir {
name: "node_modules".to_string(),
path: node_modules,
size_bytes: size,
}]
} else {
vec![]
}
}
fn enforce_lockfile(&self, project_dir: &Path, policy: EnforcePolicy) -> Result<()> {
let lockfile = project_dir.join("package-lock.json");
enforce_two_tier(
&lockfile,
"npm",
&["ci", "--dry-run", "--ignore-scripts"],
&["install", "--package-lock-only", "--ignore-scripts"],
project_dir,
policy,
)
}
fn restore(&self, project_dir: &Path) -> Result<()> {
run_command("npm", &["ci"], project_dir)
}
fn lockfiles(&self) -> &'static [&'static str] {
&["package-lock.json"]
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs;
use tempfile::tempdir;
#[test]
fn test_name() {
assert_eq!(Npm.name(), "npm");
}
#[test]
fn a_default_pass_never_rewrites_a_stale_lockfile() {
if !super::super::binary_available("npm") {
return;
}
let dir = tempdir().unwrap();
fs::write(
dir.path().join("package.json"),
r#"{"name":"stale","version":"1.0.0","dependencies":{"left-pad":"^1.3.0"}}"#,
)
.unwrap();
let stale = r#"{"name":"stale","version":"1.0.0","lockfileVersion":3,"requires":true,"packages":{"":{"name":"stale","version":"1.0.0"}}}"#;
fs::write(dir.path().join("package-lock.json"), stale).unwrap();
let result = Npm.enforce_lockfile(dir.path(), EnforcePolicy::default());
assert!(
result.is_err(),
"a lockfile out of sync with package.json must not pass verification"
);
assert_eq!(
fs::read_to_string(dir.path().join("package-lock.json")).unwrap(),
stale,
"the read-only verification rewrote package-lock.json"
);
}
#[test]
fn test_detect_positive() {
let dir = tempdir().unwrap();
fs::File::create(dir.path().join("package-lock.json")).unwrap();
assert!(Npm.detect(dir.path()));
}
#[test]
fn test_detect_negative() {
let dir = tempdir().unwrap();
assert!(!Npm.detect(dir.path()));
}
#[test]
fn test_bloat_dirs_present() {
let dir = tempdir().unwrap();
fs::create_dir(dir.path().join("node_modules")).unwrap();
let bloat = Npm.bloat_dirs(dir.path());
assert_eq!(bloat.len(), 1);
assert_eq!(bloat[0].path, dir.path().join("node_modules"));
}
#[test]
fn test_bloat_dirs_absent() {
let dir = tempdir().unwrap();
let bloat = Npm.bloat_dirs(dir.path());
assert!(bloat.is_empty());
}
}