mecha10_cli/services/package/
mod.rs1#![allow(dead_code)]
7
8mod collector;
9mod packager;
10mod types;
11mod utils;
12
13pub use types::{AssetInfo, BinaryInfo, ConfigInfo, PackageConfig, PackageManifest};
14
15use anyhow::{Context, Result};
16use chrono::Utc;
17use indicatif::{ProgressBar, ProgressStyle};
18use std::collections::HashMap;
19use std::path::PathBuf;
20use std::process::Command;
21use std::time::Duration;
22
23pub struct PackageService {
50 project_name: String,
51 project_version: String,
52 project_root: PathBuf,
53}
54
55impl PackageService {
56 pub fn new(project_name: String, project_version: String, project_root: impl Into<PathBuf>) -> Result<Self> {
64 let project_root = project_root.into();
65 if !project_root.exists() {
66 return Err(anyhow::anyhow!(
67 "Project root does not exist: {}",
68 project_root.display()
69 ));
70 }
71
72 Ok(Self {
73 project_name,
74 project_version,
75 project_root,
76 })
77 }
78
79 pub fn build(&self, config: PackageConfig) -> Result<PathBuf> {
85 self.build_binaries(&config)?;
87
88 let binaries = self.collect_binaries(&config)?;
90 let configs = self.collect_configs()?;
91 let assets = if config.include_assets {
92 self.collect_assets()?
93 } else {
94 Vec::new()
95 };
96
97 let manifest = self.create_manifest(&config, &binaries, &configs, &assets)?;
99
100 self.validate_package(&manifest)?;
102
103 let package_path = self.create_package(&config, &manifest, &binaries, &assets)?;
105
106 Ok(package_path)
107 }
108
109 fn build_binaries(&self, config: &PackageConfig) -> Result<()> {
111 let spinner = ProgressBar::new_spinner();
112 spinner.set_style(
113 ProgressStyle::default_spinner()
114 .template(" {spinner:.green} {msg}")
115 .unwrap(),
116 );
117 spinner.set_message(format!("Building for {}...", config.target_arch.as_str()));
118 spinner.enable_steady_tick(Duration::from_millis(100));
119
120 let mut cmd = Command::new("cargo");
121 cmd.arg("build").arg("--workspace").current_dir(&self.project_root);
122
123 if config.build_profile == "release" {
124 cmd.arg("--release");
125 }
126
127 cmd.arg("--target").arg(config.target_arch.as_str());
128
129 let status = cmd.status().context("Failed to run cargo build")?;
130
131 spinner.finish_and_clear();
132
133 if !status.success() {
134 return Err(anyhow::anyhow!(
135 "Build failed for target {}",
136 config.target_arch.as_str()
137 ));
138 }
139
140 Ok(())
141 }
142
143 fn collect_binaries(&self, config: &PackageConfig) -> Result<Vec<BinaryInfo>> {
145 collector::collect_binaries(&self.project_root, config)
146 }
147
148 fn collect_configs(&self) -> Result<Vec<ConfigInfo>> {
150 collector::collect_configs(&self.project_root)
151 }
152
153 fn collect_assets(&self) -> Result<Vec<AssetInfo>> {
155 collector::collect_assets(&self.project_root)
156 }
157
158 fn create_manifest(
160 &self,
161 config: &PackageConfig,
162 binaries: &[BinaryInfo],
163 configs: &[ConfigInfo],
164 assets: &[AssetInfo],
165 ) -> Result<PackageManifest> {
166 Ok(PackageManifest {
167 format_version: PackageManifest::FORMAT_VERSION.to_string(),
168 name: self.project_name.clone(),
169 version: self.project_version.clone(),
170 build_timestamp: Utc::now(),
171 target_arch: config.target_arch,
172 binaries: binaries.to_vec(),
173 configs: configs.to_vec(),
174 assets: assets.to_vec(),
175 dependencies: self.collect_dependencies()?,
176 metadata: config.custom_metadata.clone(),
177 build_profile: config.build_profile.clone(),
178 git_commit: self.get_git_commit(),
179 environment: config.environment.clone(),
180 })
181 }
182
183 fn validate_package(&self, manifest: &PackageManifest) -> Result<()> {
185 if manifest.format_version != PackageManifest::FORMAT_VERSION {
186 return Err(anyhow::anyhow!(
187 "Invalid format version: {} (expected {})",
188 manifest.format_version,
189 PackageManifest::FORMAT_VERSION
190 ));
191 }
192
193 if manifest.binaries.is_empty() {
194 return Err(anyhow::anyhow!("Package must contain at least one binary"));
195 }
196
197 for binary in &manifest.binaries {
198 if binary.name.is_empty() {
199 return Err(anyhow::anyhow!("Binary name cannot be empty"));
200 }
201 if binary.size_bytes == 0 {
202 return Err(anyhow::anyhow!("Binary {} has zero size", binary.name));
203 }
204 }
205
206 for config in &manifest.configs {
207 if config.name.is_empty() {
208 return Err(anyhow::anyhow!("Config name cannot be empty"));
209 }
210 }
211
212 Ok(())
213 }
214
215 fn create_package(
217 &self,
218 config: &PackageConfig,
219 manifest: &PackageManifest,
220 binaries: &[BinaryInfo],
221 assets: &[AssetInfo],
222 ) -> Result<PathBuf> {
223 packager::create_package(
224 &self.project_root,
225 &self.project_name,
226 &self.project_version,
227 config,
228 manifest,
229 binaries,
230 assets,
231 )
232 }
233
234 fn collect_dependencies(&self) -> Result<HashMap<String, String>> {
236 utils::collect_dependencies(&self.project_root)
237 }
238
239 fn get_git_commit(&self) -> Option<String> {
241 utils::get_git_commit(&self.project_root)
242 }
243}