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