use std::fs;
use std::hash::{DefaultHasher, Hash, Hasher};
use std::path::Path;
use crate::error::CommandError;
use crate::process::{ProcessSpec, run};
use crate::project::{self, ProjectConfig};
use crate::tool::{Tool, probe, warn_if_unverified};
const FINGERPRINT_FILE: &str = ".arcature-install-fingerprint";
pub(crate) fn execute() -> Result<(), CommandError> {
let project = project::discover()?;
frontend(&project)
}
pub(crate) fn frontend(project: &ProjectConfig) -> Result<(), CommandError> {
for tool in [Tool::Node, Tool::Pnpm] {
warn_if_unverified(tool, probe(tool)?);
}
let frontend = project.frontend_root();
let package_json = frontend.join("package.json");
let lockfile = frontend.join("pnpm-lock.yaml");
let node_modules = frontend.join("node_modules");
let fingerprint_path = node_modules.join(FINGERPRINT_FILE);
if let Some(stored) = read_fingerprint(&fingerprint_path)
&& let Some(current) = fingerprint(&package_json, &lockfile)
&& node_modules.is_dir()
&& current == stored
{
println!("frontend dependencies up to date");
return Ok(());
}
if lockfile.is_file() {
println!("frontend installing locked dependencies");
if let Err(error) = run(&ProcessSpec::new(Tool::Pnpm.executable(), &frontend)
.args(["install", "--frozen-lockfile"]))
{
eprintln!(
"hint `pnpm-lock.yaml` may be out of sync with `package.json` — \
run `pnpm install` in {} to regenerate it, then retry",
frontend.display()
);
return Err(error.into());
}
} else {
println!("frontend first dependency install (creating pnpm-lock.yaml)");
run(&ProcessSpec::new(Tool::Pnpm.executable(), &frontend).args(["install"]))?;
}
if let Some(fp) = fingerprint(&package_json, &lockfile)
&& let Some(parent) = fingerprint_path.parent()
&& fs::create_dir_all(parent).is_ok()
&& fs::write(&fingerprint_path, fp).is_err()
{
eprintln!("warn cannot record install fingerprint; next start will reinstall");
}
Ok(())
}
fn fingerprint(package_json: &Path, lockfile: &Path) -> Option<String> {
let pkg = fs::read(package_json).ok()?;
let mut hasher = DefaultHasher::new();
pkg.hash(&mut hasher);
if lockfile.is_file()
&& let Ok(lock) = fs::read(lockfile)
{
lock.hash(&mut hasher);
}
Some(format!("{:016x}", hasher.finish()))
}
fn read_fingerprint(path: &Path) -> Option<String> {
fs::read_to_string(path).ok().map(|s| s.trim().to_owned())
}