forge_core_local_deployment/
lib.rs1use std::{collections::HashMap, sync::Arc};
2
3use async_trait::async_trait;
4use forge_core_db::DBService;
5use forge_core_deployment::{Deployment, DeploymentError};
6use forge_core_executors::profile::ExecutorConfigs;
7use forge_core_services::services::{
8 analytics::{AnalyticsConfig, AnalyticsContext, AnalyticsService, generate_user_id},
9 approvals::Approvals,
10 auth::AuthService,
11 config::{Config, load_config_from_file, save_config_to_file},
12 container::ContainerService,
13 drafts::DraftsService,
14 events::EventService,
15 file_search_cache::FileSearchCache,
16 filesystem::FilesystemService,
17 forge_config::ForgeConfigService,
18 git::GitService,
19 image::ImageService,
20 omni::{OmniConfig, OmniService},
21 profile_loader::ProfileCacheManager,
22};
23use forge_core_utils::{assets::config_path, msg_store::MsgStore};
24use tokio::sync::RwLock;
25use uuid::Uuid;
26
27use crate::container::LocalContainerService;
28mod command;
29pub mod container;
30
31#[derive(Clone)]
32pub struct LocalDeployment {
33 config: Arc<RwLock<Config>>,
34 user_id: String,
35 db: DBService,
36 analytics: Option<AnalyticsService>,
37 msg_stores: Arc<RwLock<HashMap<Uuid, Arc<MsgStore>>>>,
38 container: LocalContainerService,
39 git: GitService,
40 auth: AuthService,
41 image: ImageService,
42 filesystem: FilesystemService,
43 events: EventService,
44 file_search_cache: Arc<FileSearchCache>,
45 approvals: Approvals,
46 drafts: DraftsService,
47 forge_config: ForgeConfigService,
48 omni: Arc<RwLock<OmniService>>,
49 profile_cache: ProfileCacheManager,
50}
51
52#[async_trait]
53impl Deployment for LocalDeployment {
54 async fn new() -> Result<Self, DeploymentError> {
55 let mut raw_config = load_config_from_file(&config_path()).await;
56
57 let profiles = ExecutorConfigs::get_cached();
58 if !raw_config.onboarding_acknowledged
59 && let Ok(recommended_executor) = profiles.get_recommended_executor_profile().await
60 {
61 raw_config.executor_profile = recommended_executor;
62 }
63
64 {
66 let current_version = forge_core_utils::version::APP_VERSION;
67 let stored_version = raw_config.last_app_version.as_deref();
68
69 if stored_version != Some(current_version) {
70 raw_config.show_release_notes = stored_version.is_some();
72 raw_config.last_app_version = Some(current_version.to_string());
73 }
74 }
75
76 save_config_to_file(&raw_config, &config_path()).await?;
78
79 let config = Arc::new(RwLock::new(raw_config));
80 let user_id = generate_user_id();
81 let analytics = AnalyticsConfig::new().map(AnalyticsService::new);
82 let git = GitService::new();
83 let msg_stores = Arc::new(RwLock::new(HashMap::new()));
84 let auth = AuthService::new();
85 let filesystem = FilesystemService::new();
86
87 let events_msg_store = Arc::new(MsgStore::new());
89 let events_entry_count = Arc::new(RwLock::new(0));
90
91 let db = {
93 let hook = EventService::create_hook(
94 events_msg_store.clone(),
95 events_entry_count.clone(),
96 DBService::new().await?, );
98 DBService::new_with_after_connect(hook).await?
99 };
100
101 let image = ImageService::new(db.clone().pool)?;
102 {
103 let image_service = image.clone();
104 tokio::spawn(async move {
105 tracing::info!("Starting orphaned image cleanup...");
106 if let Err(e) = image_service.delete_orphaned_images().await {
107 tracing::error!("Failed to clean up orphaned images: {}", e);
108 }
109 });
110 }
111
112 let approvals = Approvals::new(msg_stores.clone());
113
114 let analytics_ctx = analytics.as_ref().map(|s| AnalyticsContext {
117 user_id: user_id.clone(),
118 analytics_service: s.clone(),
119 });
120 let container = LocalContainerService::new(
121 db.clone(),
122 msg_stores.clone(),
123 config.clone(),
124 git.clone(),
125 image.clone(),
126 analytics_ctx,
127 approvals.clone(),
128 );
129 container.spawn_worktree_cleanup().await;
130
131 let events = EventService::new(db.clone(), events_msg_store, events_entry_count);
132 let drafts = DraftsService::new(db.clone(), image.clone());
133 let file_search_cache = Arc::new(FileSearchCache::new());
134
135 let forge_config = ForgeConfigService::new(db.pool.clone());
137 let omni = Arc::new(RwLock::new(OmniService::new(OmniConfig::default())));
138 let profile_cache = ProfileCacheManager::new();
139
140 Ok(Self {
141 config,
142 user_id,
143 db,
144 analytics,
145 msg_stores,
146 container,
147 git,
148 auth,
149 image,
150 filesystem,
151 events,
152 file_search_cache,
153 approvals,
154 drafts,
155 forge_config,
156 omni,
157 profile_cache,
158 })
159 }
160
161 fn user_id(&self) -> &str {
162 &self.user_id
163 }
164
165 fn shared_types() -> Vec<String> {
166 vec![]
167 }
168
169 fn config(&self) -> &Arc<RwLock<Config>> {
170 &self.config
171 }
172
173 fn db(&self) -> &DBService {
174 &self.db
175 }
176
177 fn analytics(&self) -> &Option<AnalyticsService> {
178 &self.analytics
179 }
180
181 fn container(&self) -> &impl ContainerService {
182 &self.container
183 }
184 fn auth(&self) -> &AuthService {
185 &self.auth
186 }
187
188 fn git(&self) -> &GitService {
189 &self.git
190 }
191
192 fn image(&self) -> &ImageService {
193 &self.image
194 }
195
196 fn filesystem(&self) -> &FilesystemService {
197 &self.filesystem
198 }
199
200 fn msg_stores(&self) -> &Arc<RwLock<HashMap<Uuid, Arc<MsgStore>>>> {
201 &self.msg_stores
202 }
203
204 fn events(&self) -> &EventService {
205 &self.events
206 }
207
208 fn file_search_cache(&self) -> &Arc<FileSearchCache> {
209 &self.file_search_cache
210 }
211
212 fn approvals(&self) -> &Approvals {
213 &self.approvals
214 }
215
216 fn drafts(&self) -> &DraftsService {
217 &self.drafts
218 }
219
220 fn forge_config(&self) -> &ForgeConfigService {
221 &self.forge_config
222 }
223
224 fn omni(&self) -> &Arc<RwLock<OmniService>> {
225 &self.omni
226 }
227
228 fn profile_cache(&self) -> &ProfileCacheManager {
229 &self.profile_cache
230 }
231}