rivox 1.0.0

Universal polyglot build coordination layer for Python, Rust, and Node monorepos
Documentation
use anyhow::{Context, Result};
use serde::{Deserialize, Serialize};
use std::fs;
use std::path::Path;

#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
pub struct RivoxLockfile {
    pub version: u32,
    pub created_at: String,
    #[serde(default)]
    pub ecosystems: Vec<LockedEcosystem>,
    #[serde(default)]
    pub cross_refs: Vec<LockedCrossRef>,
    #[serde(default)]
    pub graph_nodes: Vec<LockedGraphNode>,
    pub provenance: Option<LockedProvenance>,
}

#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
pub struct LockedEcosystem {
    pub name: String,
    pub tool: String,
    pub tool_version: String,
    pub lockfile_path: String,
    pub lockfile_hash: String,
}

#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
pub struct LockedCrossRef {
    pub from: String,
    pub to: String,
    pub hash: String,
}

#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
pub struct LockedGraphNode {
    pub ecosystem: String,
    pub package: String,
    pub version: String,
    pub content_hash: String,
}

#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
pub struct LockedProvenance {
    pub bundle_path: String,
    pub signature_ref: String,
}

impl RivoxLockfile {
    pub fn load_from_file(path: &Path) -> Result<Self> {
        let content = fs::read_to_string(path)
            .with_context(|| format!("Failed to read lockfile at {}", path.display()))?;
        let lockfile: RivoxLockfile = toml::from_str(&content)
            .with_context(|| format!("Failed to parse TOML lockfile at {}", path.display()))?;
        Ok(lockfile)
    }

    pub fn save_to_file(&self, path: &Path) -> Result<()> {
        let content =
            toml::to_string_pretty(self).context("Failed to serialize rivox.lock TOML")?;
        fs::write(path, content)
            .with_context(|| format!("Failed to write lockfile to {}", path.display()))?;
        Ok(())
    }
}