1use 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}