shine-cli 1.5.0

Keep development environments portable across machines and remote sessions
Documentation
use anyhow::{Context, Result, bail};

use crate::colors;
use crate::config::Config;

use super::bootstrap::item_is_present;
use super::commands::current_unix_timestamp;
use super::detect::detect_os_id;
use super::manifest::load_sys_preset;
use super::profile::install_sys_profile_loader_with_templates;
use super::profile_compose::{compose_sys_profiles, enabled_profile_items};
use super::run_manifest::{SysRunEntry, SysRunManifest};
use super::{SysItemMode, SysItemStatus};

pub async fn handle_profile_enable(config: &Config, item_id: &str, dry_run: bool) -> Result<()> {
    handle_profile_state(config, item_id, true, dry_run).await
}

pub async fn handle_profile_disable(config: &Config, item_id: &str, dry_run: bool) -> Result<()> {
    handle_profile_state(config, item_id, false, dry_run).await
}

pub(super) async fn sync_composed_profile(
    config: &Config,
) -> Result<Option<super::SysItemOutcome>> {
    let os_id = detect_os_id().await?;
    let loaded = load_sys_preset(config, &os_id).await?;
    let run_manifest = SysRunManifest::load(config.shine_dir()).await?;
    let enabled_items = enabled_profile_items(&loaded.manifest, &run_manifest.entries, &os_id);
    let sys_shell: &'static str = config.shell_type.into();
    let templates =
        compose_sys_profiles(config, &os_id, &loaded, &enabled_items, sys_shell).await?;
    install_sys_profile_loader_with_templates(
        config,
        &os_id,
        &loaded.root,
        sys_shell,
        false,
        Some(&templates),
    )
    .await
    .map(Some)
}

async fn handle_profile_state(
    config: &Config,
    item_id: &str,
    enabled: bool,
    dry_run: bool,
) -> Result<()> {
    crate::config::print_presets_note(config);
    let os_id = detect_os_id().await?;
    let loaded = load_sys_preset(config, &os_id).await?;
    let item = loaded
        .manifest
        .items
        .iter()
        .find(|item| item.id == item_id)
        .with_context(|| format!("unknown sys item `{item_id}` for {os_id}"))?;
    if item.mode != SysItemMode::Init {
        bail!("managed sys item `{item_id}` has no bootstrap shell integration");
    }
    if item.shell.is_empty() {
        bail!("sys item `{item_id}` declares no shell integration");
    }
    if enabled && !item_is_present(config, item).await? {
        bail!(
            "sys item `{item_id}` is not currently detected; run `shine sys bootstrap {item_id}` first"
        );
    }

    let mut run_manifest = SysRunManifest::load(config.shine_dir()).await?;
    let existing = run_manifest
        .entries
        .iter_mut()
        .find(|entry| entry.os_id == os_id && entry.item_id == item_id && !entry.managed);
    match existing {
        Some(entry) => entry.profile_enabled = enabled,
        None if enabled => run_manifest.upsert(SysRunEntry {
            os_id: os_id.clone(),
            item_id: item.id.clone(),
            label: item.label.clone(),
            status: SysItemStatus::AlreadyInstalled,
            detail: "shell integration enabled after live detection".to_string(),
            updated_at: current_unix_timestamp().to_string(),
            managed: false,
            profile_enabled: true,
            receipt: None,
        }),
        None => {}
    }

    let enabled_items = enabled_profile_items(&loaded.manifest, &run_manifest.entries, &os_id);
    let sys_shell: &'static str = config.shell_type.into();
    let templates =
        compose_sys_profiles(config, &os_id, &loaded, &enabled_items, sys_shell).await?;
    let action = if enabled { "enable" } else { "disable" };
    if dry_run {
        println!(
            "{} sys/{item_id}: would {action} shell integration for {sys_shell}",
            colors::dim("[dry-run]")
        );
        return Ok(());
    }

    let outcome = install_sys_profile_loader_with_templates(
        config,
        &os_id,
        &loaded.root,
        sys_shell,
        false,
        Some(&templates),
    )
    .await?;
    run_manifest.save(config.shine_dir()).await?;
    println!(
        "{} sys/{item_id}: shell integration {} ({})",
        colors::symbol(""),
        if enabled { "enabled" } else { "disabled" },
        outcome.detail
    );
    Ok(())
}