torvyn_cli/commands/
pack.rs1use crate::cli::PackArgs;
4use crate::errors::CliError;
5use crate::output::terminal;
6use crate::output::{CommandResult, HumanRenderable, OutputContext};
7use serde::Serialize;
8use std::path::PathBuf;
9
10#[derive(Debug, Serialize)]
12pub struct PackResult {
13 pub name: String,
15 pub version: String,
17 pub artifact_path: PathBuf,
19 pub artifact_size_bytes: u64,
21 pub layers: Vec<PackLayer>,
23}
24
25#[derive(Debug, Serialize)]
27pub struct PackLayer {
28 pub name: String,
30 pub size_bytes: u64,
32}
33
34impl HumanRenderable for PackResult {
35 fn render_human(&self, ctx: &OutputContext) {
36 terminal::print_success(ctx, &format!("Packed: {}:{}", self.name, self.version));
37 terminal::print_kv(ctx, "Artifact", &self.artifact_path.display().to_string());
38 terminal::print_kv(
39 ctx,
40 "Size",
41 &terminal::format_bytes(self.artifact_size_bytes),
42 );
43 if !self.layers.is_empty() {
44 eprintln!(" Layers:");
45 for layer in &self.layers {
46 eprintln!(
47 " - {} ({})",
48 layer.name,
49 terminal::format_bytes(layer.size_bytes)
50 );
51 }
52 }
53 }
54}
55
56pub async fn execute(
60 args: &PackArgs,
61 ctx: &OutputContext,
62) -> Result<CommandResult<PackResult>, CliError> {
63 let manifest_path = &args.manifest;
64
65 if !manifest_path.exists() {
66 return Err(CliError::Config {
67 detail: format!("Manifest not found: {}", manifest_path.display()),
68 file: Some(manifest_path.display().to_string()),
69 suggestion: "Run this command from a Torvyn project directory.".into(),
70 });
71 }
72
73 let spinner = ctx.spinner("Checking contracts...");
74
75 let manifest_content = std::fs::read_to_string(manifest_path).map_err(|e| CliError::Io {
77 detail: e.to_string(),
78 path: Some(manifest_path.display().to_string()),
79 })?;
80
81 let manifest = torvyn_config::ComponentManifest::from_toml_str(
82 &manifest_content,
83 manifest_path.to_str().unwrap_or("Torvyn.toml"),
84 )
85 .map_err(|errors| CliError::Config {
86 detail: format!("Manifest has {} error(s)", errors.len()),
87 file: Some(manifest_path.display().to_string()),
88 suggestion: "Run `torvyn check` first.".into(),
89 })?;
90
91 if let Some(sp) = &spinner {
92 sp.finish_and_clear();
93 }
94
95 let project_dir = manifest_path.parent().unwrap_or(std::path::Path::new("."));
96
97 let output_dir = args
98 .output
99 .clone()
100 .unwrap_or_else(|| project_dir.join(".torvyn").join("artifacts"));
101
102 std::fs::create_dir_all(&output_dir).map_err(|e| CliError::Io {
103 detail: format!("Cannot create output directory: {e}"),
104 path: Some(output_dir.display().to_string()),
105 })?;
106
107 let name = manifest.torvyn.name.clone();
108 let version = manifest.torvyn.version.clone();
109 let tag = args.tag.clone().unwrap_or_else(|| version.clone());
110
111 let artifact_filename = format!("{name}-{tag}.tar");
114 let artifact_path = output_dir.join(&artifact_filename);
115
116 let artifact_json = serde_json::json!({
117 "name": name,
118 "version": version,
119 "tag": tag,
120 });
121 std::fs::write(
122 &artifact_path,
123 serde_json::to_string_pretty(&artifact_json).unwrap(),
124 )
125 .map_err(|e| CliError::Io {
126 detail: format!("Failed to write artifact: {e}"),
127 path: Some(artifact_path.display().to_string()),
128 })?;
129
130 let artifact_size = std::fs::metadata(&artifact_path)
131 .map(|m| m.len())
132 .unwrap_or(0);
133
134 let result = PackResult {
135 name,
136 version,
137 artifact_path,
138 artifact_size_bytes: artifact_size,
139 layers: vec![],
140 };
141
142 Ok(CommandResult {
143 success: true,
144 command: "pack".into(),
145 data: result,
146 warnings: vec![],
147 })
148}