1use crate::{error::DeployError, report::Reporter};
10use anyhow::anyhow;
11use std::{
12 path::{Path, PathBuf},
13 process::Command,
14};
15
16pub struct BuildArtifact {
18 pub source_dir: PathBuf,
20}
21
22pub trait BuildStrategy {
24 fn build(&self, reporter: &dyn Reporter) -> Result<BuildArtifact, DeployError>;
25}
26
27pub struct ViteSpaBuild {
30 pub project_path: String,
31 pub output_dir: String,
32 pub package_manager: String,
33}
34
35impl BuildStrategy for ViteSpaBuild {
36 fn build(&self, reporter: &dyn Reporter) -> Result<BuildArtifact, DeployError> {
37 reporter.step_start(&format!(
38 "Building {} with {}…",
39 self.project_path, self.package_manager
40 ));
41
42 let project_dir = Path::new(&self.project_path);
45 if !project_dir.exists() {
46 reporter.step_fail(&format!(
47 "Project path '{}' does not exist.",
48 self.project_path
49 ));
50 return Err(DeployError::Other(anyhow!(
51 "Project path '{}' does not exist. Check the 'source' field in .smb/config.toml.",
52 self.project_path
53 )));
54 }
55
56 let status = Command::new(&self.package_manager)
57 .arg("build")
58 .current_dir(&self.project_path)
59 .status()
60 .map_err(|e| {
61 reporter.step_fail(&format!("Failed to spawn '{}': {e}", self.package_manager));
62 DeployError::Other(anyhow!("Failed to spawn '{}': {e}", self.package_manager))
63 })?;
64
65 if !status.success() {
66 reporter.step_fail("Build failed. See output above.");
67 return Err(DeployError::Other(anyhow!(
68 "'{} build' exited with status {status}",
69 self.package_manager
70 )));
71 }
72
73 reporter.step_done("Build complete.");
74 Ok(BuildArtifact {
75 source_dir: project_dir.join(&self.output_dir),
76 })
77 }
78}