pray-cli 1.22.0

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

#[path = "commands_update_upstream.rs"]
mod upstream;
pub(crate) use upstream::report_path_upstream_would_conflicts;
use upstream::{
    latest_upstream_constraint_updates_json, plan_latest_upstream_constraints,
    print_latest_upstream_constraints, resolve_after_latest_upstream,
};

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 mut options = update_resolve_options(package.as_deref());
    let project = resolve_project_with_options(&manifest_path, &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();
    let upstream_plans = plan_latest_upstream_constraints(&project, package.as_deref(), &options)?;
    let upstream_constraint_updates = latest_upstream_constraint_updates_json(&upstream_plans);

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

    options.environment = project.environment.clone();
    let candidate = if !manifest_updates.is_empty() || !upstream_plans.is_empty() {
        resolve_after_latest_upstream(
            &manifest_path,
            &project.project_root,
            parse_manifest(&updated_text)?,
            &options,
            package.as_deref(),
            &upstream_plans,
            dry_run,
        )?
    } else if dry_run {
        let previous = read_lockfile(&lockfile_path()).ok();
        upstream::report_path_upstream_would_conflicts(
            &project,
            previous.as_ref(),
            package.as_deref(),
            &options,
        )?;
        project
    } else {
        let previous = read_lockfile(&lockfile_path()).ok();
        if apply_path_upstream_refreshes(&project, previous.as_ref(), package.as_deref(), &options)?
        {
            resolve_project_with_options(&manifest_path, &options)?
        } else {
            project
        }
    };
    write_update(
        candidate,
        package,
        json,
        manifest_constraint_updates,
        (updated_text != manifest_text).then_some(updated_text),
        dry_run,
        upstream_constraint_updates,
    )
}