pray-cli 1.14.0

Package manager for the language placed before inference — Prayfile CLI (binary: pray)
use super::{constraint_preview_options, update_resolve_options, write_update};
use crate::project_paths::{manifest_path, resolve_project_with_options};
use pray_core::constraint::{latest_constraint_for_package, version_satisfies};
use pray_core::manifest::{parse_manifest, read_manifest_text, replace_package_declaration};
use pray_core::{PrayError, PrayResult};

pub(super) fn update_latest_command(
    package: Option<String>,
    json: bool,
    dry_run: bool,
) -> PrayResult<()> {
    let manifest_path = manifest_path();
    let manifest_text = read_manifest_text(&manifest_path)?;
    let preview_options = constraint_preview_options();
    let project = resolve_project_with_options(&manifest_path, &preview_options)?;

    if let Some(package_name) = &package {
        if !project
            .manifest
            .packages
            .iter()
            .any(|declaration| declaration.name == *package_name)
        {
            return Err(PrayError::Manifest(format!(
                "package {package_name} not found"
            )));
        }
    }

    let mut updated_text = manifest_text.clone();
    let mut manifest_updates = Vec::new();

    for resolved in &project.packages {
        if let Some(package_name) = &package {
            if resolved.declaration.name != *package_name {
                continue;
            }
        }
        let Some(registry_latest_version) = &resolved.registry_latest_version else {
            continue;
        };
        if version_satisfies(registry_latest_version, &resolved.declaration.constraint)? {
            continue;
        }
        let new_constraint = latest_constraint_for_package(
            &resolved.declaration.constraint,
            registry_latest_version,
        )?;
        if !version_satisfies(registry_latest_version, &new_constraint)? {
            return Err(PrayError::Resolution(format!(
                "derived constraint {new_constraint} does not admit registry latest {registry_latest_version} for {}",
                resolved.declaration.name
            )));
        }
        let mut updated_declaration = resolved.declaration.clone();
        let previous_constraint = updated_declaration.constraint.clone();
        updated_declaration.constraint = new_constraint.clone();
        updated_text = replace_package_declaration(&updated_text, &updated_declaration)?;
        manifest_updates.push((
            resolved.declaration.name.clone(),
            previous_constraint,
            new_constraint,
            registry_latest_version.clone(),
        ));
    }

    let manifest_constraint_updates: Vec<serde_json::Value> = manifest_updates
        .iter()
        .map(
            |(name, previous_constraint, new_constraint, registry_latest_version)| {
                serde_json::json!({
                    "name": name,
                    "from_constraint": previous_constraint,
                    "to_constraint": new_constraint,
                    "registry_latest_version": registry_latest_version,
                })
            },
        )
        .collect();

    if manifest_updates.is_empty() {
        if !json {
            println!("All package constraints already allow registry latest versions");
        }
    } else if !json {
        for (name, previous_constraint, new_constraint, registry_latest_version) in
            &manifest_updates
        {
            println!(
                "Prayfile: {name} constraint {previous_constraint} -> {new_constraint} (registry latest {registry_latest_version})"
            );
        }
    }

    let mut options = update_resolve_options(package.as_deref());
    options.environment = project.environment;
    let candidate = pray_core::resolve::resolve_manifest_in_context(
        &manifest_path,
        &project.project_root,
        parse_manifest(&updated_text)?,
        &options,
    )?;
    write_update(
        candidate,
        package,
        json,
        manifest_constraint_updates,
        (updated_text != manifest_text).then_some(updated_text),
        dry_run,
    )
}