pray-cli 1.9.1

Package manager for the language placed before inference — Prayfile CLI (binary: pray)
use crate::lockfile_ops::{build_lockfile, ensure_rendered_outputs_current};
use crate::project_paths::{lockfile_path, manifest_path, resolve_project};
use pray_core::lockfile::{read_lockfile, write_lockfile, Lockfile};
use pray_core::render::{
    layout_rendered_targets, render_project, write_rendered_targets_with_previous_lockfile,
};
use pray_core::verify::{drift_project, format_verification_report, verify_project};
use pray_core::{PrayError, PrayResult};

pub(crate) fn render_command(check_only: bool) -> PrayResult<()> {
    let project = resolve_project(&manifest_path())?;
    let rendered = render_project(&project)?;
    if check_only {
        ensure_rendered_outputs_current(&project, &rendered)?;
        return Ok(());
    }
    let previous_lockfile = read_lockfile(&lockfile_path()).ok();
    let laid_out = layout_rendered_targets(&project, &rendered)?;
    let lockfile = build_lockfile(&project, &laid_out)?;
    write_lockfile(&lockfile_path(), &lockfile)?;
    write_rendered_targets_with_previous_lockfile(&project, &rendered, previous_lockfile.as_ref())?;
    Ok(())
}

pub(crate) fn verify_command(strict: bool) -> PrayResult<()> {
    let project = resolve_project(&manifest_path())?;
    let lockfile = read_lockfile(&lockfile_path())?;
    let report = verify_project(&project, &lockfile, strict)?;
    if !report.is_clean() {
        eprintln!("{}", format_verification_report(&report));
    }
    Ok(())
}

pub(crate) fn drift_command(semantic: bool) -> PrayResult<()> {
    let project = resolve_project(&manifest_path())?;
    let lockfile = read_lockfile(&lockfile_path())?;
    if semantic {
        drift_semantic_command(&project, &lockfile)
    } else {
        drift_project(&project, &lockfile)?;
        Ok(())
    }
}

fn drift_semantic_command(
    project: &pray_core::resolve::ResolvedProject,
    lockfile: &Lockfile,
) -> PrayResult<()> {
    let lock_versions: std::collections::BTreeMap<&str, (&str, usize)> = lockfile
        .package
        .iter()
        .map(|package| {
            let managed_span_count = lockfile
                .managed_span
                .iter()
                .filter(|span| span.package == package.name)
                .count();
            (
                package.name.as_str(),
                (package.version.as_str(), managed_span_count),
            )
        })
        .collect();

    let mut lines = Vec::new();
    for package in &project.packages {
        let Some((locked_version, managed_span_count)) =
            lock_versions.get(package.declaration.name.as_str())
        else {
            continue;
        };
        if *locked_version != package.spec.version {
            lines.push(format!(
                "{} {} -> {} would change {} managed spans",
                package.declaration.name, locked_version, package.spec.version, managed_span_count,
            ));
        }
    }

    if lines.is_empty() {
        return Ok(());
    }

    let mut report = String::from("Semantic diff");
    for line in lines {
        report.push('\n');
        report.push_str(&line);
    }
    Err(PrayError::Verify(report))
}