systemprompt_sync/deploy/
artifacts.rs1use std::path::{Path, PathBuf};
12
13use systemprompt_cloud::ProjectContext;
14use systemprompt_extension::{AssetPaths, ExtensionRegistry};
15use systemprompt_models::paths::constants::build;
16
17use crate::error::{SyncError, SyncResult};
18
19struct ProjectAssetPaths {
20 storage_files: PathBuf,
21 web_dist: PathBuf,
22}
23
24impl AssetPaths for ProjectAssetPaths {
25 fn storage_files(&self) -> &Path {
26 &self.storage_files
27 }
28 fn web_dist(&self) -> &Path {
29 &self.web_dist
30 }
31}
32
33#[derive(Debug)]
34pub struct DeployArtifacts {
35 pub binary: PathBuf,
36 pub dockerfile: PathBuf,
37 project_root: PathBuf,
38}
39
40impl DeployArtifacts {
41 pub fn resolve(project_root: &Path, profile_name: &str) -> SyncResult<Self> {
42 let binary = project_root
43 .join(build::CARGO_TARGET)
44 .join("release")
45 .join(build::BINARY_NAME);
46
47 let ctx = ProjectContext::new(project_root.to_path_buf());
48 let dockerfile = ctx.profile_dockerfile(profile_name);
49
50 let artifacts = Self {
51 binary,
52 dockerfile,
53 project_root: project_root.to_path_buf(),
54 };
55 artifacts.validate()?;
56 Ok(artifacts)
57 }
58
59 fn validate(&self) -> SyncResult<()> {
60 if !self.binary.exists() {
61 return Err(SyncError::BuildArtifacts(format!(
62 "Release binary not found: {}\n\nRun: cargo build --release --bin systemprompt",
63 self.binary.display()
64 )));
65 }
66
67 self.validate_extension_assets()?;
68 self.validate_storage_directory()?;
69 self.validate_templates_directory()?;
70
71 if !self.dockerfile.exists() {
72 return Err(SyncError::BuildArtifacts(format!(
73 "Dockerfile not found: {}\n\nCreate a Dockerfile at this location",
74 self.dockerfile.display()
75 )));
76 }
77
78 Ok(())
79 }
80
81 fn validate_extension_assets(&self) -> SyncResult<()> {
82 let paths = ProjectAssetPaths {
83 storage_files: self.project_root.join("storage/files"),
84 web_dist: self.project_root.join("web/dist"),
85 };
86 let registry = ExtensionRegistry::discover()?;
87 let mut missing = Vec::new();
88 let mut outside_context = Vec::new();
89
90 for ext in registry.asset_extensions() {
91 let ext_id = ext.id();
92 for asset in ext.required_assets(&paths) {
93 if !asset.is_required() {
94 continue;
95 }
96
97 let source = asset.source();
98
99 if !source.exists() {
100 missing.push(format!("[ext:{}] {}", ext_id, source.display()));
101 continue;
102 }
103
104 if !source.starts_with(&self.project_root) {
105 outside_context.push(format!(
106 "[ext:{}] {} (not under {})",
107 ext_id,
108 source.display(),
109 self.project_root.display()
110 ));
111 }
112 }
113 }
114
115 if !missing.is_empty() {
116 return Err(SyncError::BuildArtifacts(format!(
117 "Missing required extension assets:\n {}\n\nCreate these files or mark them as \
118 optional.",
119 missing.join("\n ")
120 )));
121 }
122
123 if !outside_context.is_empty() {
124 return Err(SyncError::BuildArtifacts(format!(
125 "Extension assets outside Docker build context:\n {}\n\nMove assets inside the \
126 project directory.",
127 outside_context.join("\n ")
128 )));
129 }
130
131 Ok(())
132 }
133
134 fn validate_storage_directory(&self) -> SyncResult<()> {
135 let storage_dir = self.project_root.join("storage");
136
137 if !storage_dir.exists() {
138 return Err(SyncError::BuildArtifacts(format!(
139 "Storage directory not found: {}\n\nExpected: storage/\n\nCreate this directory \
140 for files, images, and other assets.",
141 storage_dir.display()
142 )));
143 }
144
145 let files_dir = storage_dir.join("files");
146 if !files_dir.exists() {
147 return Err(SyncError::BuildArtifacts(format!(
148 "Storage files directory not found: {}\n\nExpected: storage/files/\n\nThis \
149 directory is required for serving static assets.",
150 files_dir.display()
151 )));
152 }
153
154 Ok(())
155 }
156
157 fn validate_templates_directory(&self) -> SyncResult<()> {
158 let templates_dir = self.project_root.join("services/web/templates");
159
160 if !templates_dir.exists() {
161 return Err(SyncError::BuildArtifacts(format!(
162 "Templates directory not found: {}\n\nExpected: \
163 services/web/templates/\n\nCreate this directory with your HTML templates.",
164 templates_dir.display()
165 )));
166 }
167
168 Ok(())
169 }
170}