use std::collections::BTreeMap;
use std::path::Path;
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
use super::InitError;
use super::render::{Host, Merge};
pub const LOCK_PATH: &str = ".weftos/agents.lock.json";
pub const SCHEMA: u32 = 1;
pub const RENDERER: &str = "weftos-init/1";
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct LockFile {
pub path: String,
pub host: Host,
pub sha256: String,
pub merge: Merge,
}
#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
pub struct LockAgent {
pub id: String,
pub weftos_version: String,
pub commit: String,
pub files: Vec<LockFile>,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct Lock {
pub schema: u32,
pub renderer: String,
pub weftos_version: String,
pub commit: String,
pub source: String,
#[serde(default)]
pub team: Option<String>,
#[serde(default)]
pub agents: Vec<LockAgent>,
}
impl Lock {
pub fn empty() -> Self {
Self {
schema: SCHEMA,
renderer: RENDERER.into(),
weftos_version: String::new(),
commit: String::new(),
source: String::new(),
team: None,
agents: Vec::new(),
}
}
pub fn read(root: &Path) -> Result<Option<Self>, InitError> {
let path = root.join(LOCK_PATH);
if !path.exists() {
return Ok(None);
}
let text = std::fs::read_to_string(&path)?;
serde_json::from_str(&text)
.map(Some)
.map_err(|e| InitError::Other(format!("{}: {e}", path.display())))
}
pub fn to_json(&self) -> String {
let mut s = serde_json::to_string_pretty(self).expect("lock serializes");
s.push('\n');
s
}
pub fn files_by_path(&self) -> BTreeMap<&str, (&str, &LockFile)> {
self.agents
.iter()
.flat_map(|a| {
a.files
.iter()
.map(move |f| (f.path.as_str(), (a.id.as_str(), f)))
})
.collect()
}
pub fn agents_for(&self, host: Host) -> Vec<String> {
self.agents
.iter()
.filter(|a| a.files.iter().any(|f| f.host == host))
.map(|a| a.id.clone())
.collect()
}
pub fn normalize(&mut self) {
for a in &mut self.agents {
a.files.sort_by(|x, y| x.path.cmp(&y.path));
}
self.agents.retain(|a| !a.files.is_empty());
self.agents.sort_by(|x, y| x.id.cmp(&y.id));
}
}
pub fn sha256_hex(bytes: &[u8]) -> String {
Sha256::digest(bytes)
.iter()
.map(|b| format!("{b:02x}"))
.collect()
}