1use crate::{
2 pkg::{hash, local, types},
3 project,
4};
5use anyhow::{Result, anyhow};
6use std::collections::HashMap;
7
8pub fn run() -> Result<()> {
9 println!("Verifying project integrity with zoi.lock...");
10
11 let lockfile = project::lockfile::read_zoi_lock()?;
12 let installed_packages = local::get_installed_packages()?
13 .into_iter()
14 .filter(|p| p.scope == types::Scope::Project)
15 .collect::<Vec<_>>();
16
17 let mut lockfile_pkgs_map = HashMap::new();
18 for (reg_key, pkgs) in &lockfile.details {
19 for (short_id, detail) in pkgs {
20 let full_id = format!("{}{}", reg_key, short_id);
21 lockfile_pkgs_map.insert(full_id, detail);
22 }
23 }
24
25 let mut installed_pkgs_map = HashMap::new();
26 for installed_pkg in &installed_packages {
27 let name_with_sub = if let Some(sub) = &installed_pkg.sub_package {
28 format!("{}:{}", installed_pkg.name, sub)
29 } else {
30 installed_pkg.name.clone()
31 };
32 let full_id = format!(
33 "#{}@{}/{}",
34 installed_pkg.registry_handle, installed_pkg.repo, name_with_sub
35 );
36 installed_pkgs_map.insert(full_id, installed_pkg);
37 }
38
39 for (full_id, lock_detail) in &lockfile_pkgs_map {
40 if let Some(installed_pkg) = installed_pkgs_map.get(full_id) {
41 if installed_pkg.version != lock_detail.version {
42 return Err(anyhow!(
43 "Version mismatch for '{}': lockfile requires v{}, but v{} is installed.",
44 full_id,
45 lock_detail.version,
46 installed_pkg.version
47 ));
48 }
49
50 let parts: Vec<&str> = full_id.split('@').collect();
51 let registry_handle = parts[0].strip_prefix('#').unwrap();
52 let repo_and_name_with_sub = parts[1];
53
54 if let Some(last_slash_idx) = repo_and_name_with_sub.rfind('/') {
55 let (repo, name_with_sub) = repo_and_name_with_sub.split_at(last_slash_idx);
56 let name_with_sub = &name_with_sub[1..];
57
58 let name = if let Some(colon_idx) = name_with_sub.rfind(':') {
59 &name_with_sub[..colon_idx]
60 } else {
61 name_with_sub
62 };
63
64 let package_dir =
65 local::get_package_dir(types::Scope::Project, registry_handle, repo, name)?;
66 let latest_dir = package_dir.join("latest");
67 if !latest_dir.exists() {
68 return Err(anyhow!(
69 "Package '{}' is missing from the project's .zoi directory, though it is in the manifest.",
70 full_id
71 ));
72 }
73 let integrity = hash::calculate_dir_hash(&latest_dir)?;
74 if integrity != lock_detail.integrity {
75 return Err(anyhow!(
76 "Integrity check failed for '{}'. The installed files do not match the lockfile. Your project is in an inconsistent state.",
77 full_id
78 ));
79 }
80 } else {
81 return Err(anyhow!(
82 "Invalid package ID format in lockfile: {}",
83 full_id
84 ));
85 }
86 } else {
87 return Err(anyhow!(
88 "Package '{}' from zoi.lock is not installed.",
89 full_id
90 ));
91 }
92 }
93
94 for full_id in installed_pkgs_map.keys() {
95 if !lockfile_pkgs_map.contains_key(full_id) {
96 return Err(anyhow!(
97 "Package '{}' is installed in the project but is not in zoi.lock.",
98 full_id
99 ));
100 }
101 }
102
103 println!("Project is consistent with zoi.lock.");
104 Ok(())
105}