claude/smbcloud-github-app-k9641p
claude/smbcloud-github-app-k9641p
development
feature/apps
feature/build-release
feature/ci-docs
feature/ci-ruby-gems
feature/cicd
feature/completion
feature/dist-nuget
feature/fix-ci-sdk-pypi-rubygem
feature/fix-clippy-on-stable-channel
feature/fix-deployment-script
feature/github-email
feature/gresiq-v1
feature/project-new-structure
feature/rsync-deploy
feature/ruby-runner
feature/sdk-npm
feature/sdk-workflows
feature/smbcloud-cli-release-npm
feature/smbcloud-deploy
feature/wasm
main
release/v0.7.4
| 1 | //! Local build strategies. |
| 2 | //! |
| 3 | //! A [`BuildStrategy`] runs a project's build locally and reports where the |
| 4 | //! shippable output is, as a [`BuildArtifact`]. Transport (see |
| 5 | //! [`crate::transport`]) takes it from there. Keeping "build" and "ship" |
| 6 | //! separate is what lets the same engine drive a local build, a CI build, or a |
| 7 | //! server-side build without the strategies knowing which. |
| 8 | |
| 9 | use crate::{error::DeployError, report::Reporter}; |
| 10 | use anyhow::anyhow; |
| 11 | use std::{ |
| 12 | path::{Path, PathBuf}, |
| 13 | process::Command, |
| 14 | }; |
| 15 | |
| 16 | /// What a build produced and where to ship it from. |
| 17 | pub struct BuildArtifact { |
| 18 | /// Local directory whose contents should be shipped. |
| 19 | pub source_dir: PathBuf, |
| 20 | } |
| 21 | |
| 22 | /// Builds a project locally into a [`BuildArtifact`]. |
| 23 | pub trait BuildStrategy { |
| 24 | fn build(&self, reporter: &dyn Reporter) -> Result<BuildArtifact, DeployError>; |
| 25 | } |
| 26 | |
| 27 | /// A Vite / SPA build: run `<package_manager> build` in the project directory |
| 28 | /// and ship its output directory (e.g. `dist`). |
| 29 | pub struct ViteSpaBuild { |
| 30 | pub project_path: String, |
| 31 | pub output_dir: String, |
| 32 | pub package_manager: String, |
| 33 | } |
| 34 | |
| 35 | impl 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 | // Validate up front: `current_dir` on a missing path only surfaces as a |
| 43 | // confusing spawn error at status() time. |
| 44 | 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 | } |