nex-pkg 0.25.3

Package manager UX for nix-darwin + homebrew
Documentation
use std::path::Path;

use anyhow::Result;
use serde::Serialize;

use crate::machine_profile::{MachineProfileDocument, MACHINE_PROFILE_FILE};

pub fn run_validate(path: &Path) -> Result<()> {
    MachineProfileDocument::from_path(path)?;
    eprintln!("{} is valid", display_input(path));
    Ok(())
}

pub fn run_inspect(path: &Path, json: bool) -> Result<()> {
    let document = MachineProfileDocument::from_path(path)?;
    if json {
        println!(
            "{}",
            serde_json::to_string_pretty(&MachineProfileInspect::from(&document))?
        );
        return Ok(());
    }

    let profile = &document.machine_profile;

    print_section("Machine Profile");
    print_kv("ID", &profile.id);
    print_kv("Slug", &profile.slug);
    print_kv("Name", &profile.name);
    print_kv("Version", &profile.version);
    print_kv("Schema", &profile.schema);
    print_kv("Min Nex", &profile.min_nex);
    if let Some(description) = &profile.description {
        print_kv("Description", description);
    }
    if let Some(license) = &profile.license {
        print_kv("License", license);
    }

    print_section("Defaults");
    print_kv("Mode", &profile.defaults.mode.to_string());
    print_kv("Target", &profile.defaults.target.to_string());

    print_section("Safety");
    print_kv(
        "Default destructive",
        &profile.safety.default_destructive.to_string(),
    );
    print_kv(
        "Requires confirmation",
        &profile.safety.requires_confirmation.to_string(),
    );
    print_kv(
        "Requires target attestation",
        &profile.safety.requires_target_attestation.to_string(),
    );
    let allowed_targets = profile
        .safety
        .allowed_targets
        .iter()
        .map(ToString::to_string)
        .collect::<Vec<_>>()
        .join(", ");
    print_kv("Allowed targets", &allowed_targets);

    if let Some(secrets) = &profile.secrets {
        print_section("Secrets");
        print_kv("Required", &secrets.required.join(", "));
        print_kv("Optional", &secrets.optional.join(", "));
    }

    if !document.dependencies.is_empty() {
        print_section("Dependencies");
        for dependency in &document.dependencies {
            print_kv(
                &format!("{}:{}", dependency.kind, dependency.id),
                &format!("{} required={}", dependency.version, dependency.required),
            );
        }
    }

    Ok(())
}

#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
struct MachineProfileInspect {
    kind: &'static str,
    schema: String,
    id: String,
    slug: String,
    name: String,
    version: String,
    description: Option<String>,
    license: Option<String>,
    min_nex: String,
    defaults: MachineProfileDefaultsInspect,
    safety: MachineProfileSafetyInspect,
    secrets: MachineProfileSecretsInspect,
    dependencies: Vec<MachineProfileDependencyInspect>,
}

#[derive(Serialize)]
struct MachineProfileDefaultsInspect {
    mode: String,
    target: String,
}

#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
struct MachineProfileSafetyInspect {
    default_destructive: bool,
    requires_confirmation: bool,
    requires_target_attestation: bool,
    allowed_targets: Vec<String>,
}

#[derive(Serialize)]
struct MachineProfileSecretsInspect {
    required: Vec<String>,
    optional: Vec<String>,
}

#[derive(Serialize)]
struct MachineProfileDependencyInspect {
    kind: String,
    id: String,
    version: String,
    required: bool,
}

impl From<&MachineProfileDocument> for MachineProfileInspect {
    fn from(document: &MachineProfileDocument) -> Self {
        let profile = &document.machine_profile;
        let secrets = profile.secrets.as_ref();
        Self {
            kind: "machine-profile",
            schema: profile.schema.clone(),
            id: profile.id.clone(),
            slug: profile.slug.clone(),
            name: profile.name.clone(),
            version: profile.version.clone(),
            description: profile.description.clone(),
            license: profile.license.clone(),
            min_nex: profile.min_nex.clone(),
            defaults: MachineProfileDefaultsInspect {
                mode: profile.defaults.mode.to_string(),
                target: profile.defaults.target.to_string(),
            },
            safety: MachineProfileSafetyInspect {
                default_destructive: profile.safety.default_destructive,
                requires_confirmation: profile.safety.requires_confirmation,
                requires_target_attestation: profile.safety.requires_target_attestation,
                allowed_targets: profile
                    .safety
                    .allowed_targets
                    .iter()
                    .map(ToString::to_string)
                    .collect(),
            },
            secrets: MachineProfileSecretsInspect {
                required: secrets.map(|s| s.required.clone()).unwrap_or_default(),
                optional: secrets.map(|s| s.optional.clone()).unwrap_or_default(),
            },
            dependencies: document
                .dependencies
                .iter()
                .map(|dependency| MachineProfileDependencyInspect {
                    kind: dependency.kind.to_string(),
                    id: dependency.id.clone(),
                    version: dependency.version.clone(),
                    required: dependency.required,
                })
                .collect(),
        }
    }
}

fn print_section(label: &str) {
    println!("\n{label}");
}

fn print_kv(key: &str, value: &str) {
    println!("  {key}: {value}");
}

fn display_input(path: &Path) -> String {
    if path.is_dir() {
        path.join(MACHINE_PROFILE_FILE).display().to_string()
    } else {
        path.display().to_string()
    }
}