Skip to main content

cli/
preset_authoring.rs

1//! Terminal adapter for hypothetical Preset authoring plans.
2
3use crate::commands::{PresetPlatform, PresetReportFormat};
4use anyhow::Result;
5use shine_core::runtime::{PresetAuthoringPlanReportV1, PresetDiagnosticSeverity, RuntimePlatform};
6use std::path::Path;
7
8pub async fn handle_plan(
9    path: &Path,
10    platform: PresetPlatform,
11    format: PresetReportFormat,
12) -> Result<bool> {
13    let cwd = std::env::current_dir().unwrap_or_else(|_| Path::new(".").to_path_buf());
14    let report = shine_core::runtime::plan_preset_path(
15        &shine_core::runtime::RealHost,
16        &cwd,
17        path,
18        runtime_platform(platform),
19    )
20    .await;
21    match format {
22        PresetReportFormat::Text => print_text_report(&report),
23        PresetReportFormat::Json => println!("{}", serde_json::to_string_pretty(&report)?),
24    }
25    Ok(report.valid)
26}
27
28fn runtime_platform(platform: PresetPlatform) -> RuntimePlatform {
29    match platform {
30        PresetPlatform::Macos => RuntimePlatform::Macos,
31        PresetPlatform::Linux => RuntimePlatform::Linux,
32        PresetPlatform::Windows => RuntimePlatform::Windows,
33    }
34}
35
36fn print_text_report(report: &PresetAuthoringPlanReportV1) {
37    let target = report.target.as_deref().unwrap_or("invalid input");
38    println!("Preset authoring plan: {target} ({})", report.platform);
39    println!(
40        "  Assumptions: lifecycle state {}, environment {}, secrets {}, trust grants {}, detected commands {}, administrator {}",
41        report.assumptions.lifecycle_state,
42        report.assumptions.environment,
43        report.assumptions.secrets,
44        report.assumptions.trust_grants,
45        report.assumptions.detected_commands,
46        if report.assumptions.administrator {
47            "available"
48        } else {
49            "unavailable"
50        }
51    );
52    for diagnostic in &report.diagnostics {
53        let severity = match diagnostic.severity {
54            PresetDiagnosticSeverity::Error => "error",
55            PresetDiagnosticSeverity::Warning => "warning",
56        };
57        println!("  {severity}[{}]: {}", diagnostic.code, diagnostic.message);
58    }
59    for plan in &report.plans {
60        println!(
61            "  {} {} · {}",
62            if plan.ready { "READY" } else { "BLOCKED" },
63            plan.kind,
64            plan.operation.as_str()
65        );
66        println!("    Target: {}", plan.target);
67        println!("    Steps:");
68        if plan.steps.is_empty() {
69            println!("      - none");
70        }
71        for step in &plan.steps {
72            let resource = step
73                .resource
74                .as_deref()
75                .map(|value| format!(" · {value}"))
76                .unwrap_or_default();
77            let diagnostics = if step.diagnostic_codes.is_empty() {
78                String::new()
79            } else {
80                format!(" [{}]", step.diagnostic_codes.join(", "))
81            };
82            println!(
83                "      {} {}{}{}",
84                crate::lifecycle_plan::action_name(step.action),
85                step.target,
86                resource,
87                diagnostics
88            );
89        }
90        println!("    Required permissions:");
91        if plan.permissions.required.is_empty() {
92            println!("      - none");
93        }
94        for permission in plan.permissions.required.iter() {
95            println!(
96                "      - {}",
97                crate::lifecycle_plan::permission_name(permission)
98            );
99        }
100        for permission in plan.permissions.missing_declarations.iter() {
101            println!(
102                "      ! missing declaration: {}",
103                crate::lifecycle_plan::permission_name(permission)
104            );
105        }
106        for code in &plan.permissions.uncomputable_codes {
107            println!("      ! uncomputable: {code}");
108        }
109    }
110    println!(
111        "Result: {}",
112        if !report.valid {
113            "invalid"
114        } else if report.ready {
115            "hypothetical plan ready"
116        } else {
117            "hypothetical plan blocked under these assumptions"
118        }
119    );
120}
121
122#[cfg(test)]
123mod tests {
124    use super::*;
125
126    #[test]
127    fn platform_mapping_is_explicit() {
128        assert_eq!(
129            runtime_platform(PresetPlatform::Macos),
130            RuntimePlatform::Macos
131        );
132        assert_eq!(
133            runtime_platform(PresetPlatform::Linux),
134            RuntimePlatform::Linux
135        );
136        assert_eq!(
137            runtime_platform(PresetPlatform::Windows),
138            RuntimePlatform::Windows
139        );
140    }
141}