use std::path::Path;
use systemprompt_extension::{AssetDefinition, ExtensionRegistry};
use systemprompt_models::AppPaths;
use systemprompt_traits::JobResult;
use crate::error::{GeneratorResult as Result, PublishError};
pub async fn execute_copy_extension_assets(paths: &AppPaths) -> Result<JobResult> {
let start_time = std::time::Instant::now();
tracing::info!("Copy extension assets job started");
let registry = ExtensionRegistry::discover().map_err(PublishError::other)?;
let assets = registry.all_required_assets(paths);
if assets.is_empty() {
let duration_ms = start_time.elapsed().as_millis() as u64;
tracing::info!(duration_ms, "No extension assets to copy");
return Ok(JobResult::success()
.with_stats(0, 0)
.with_duration(duration_ms));
}
let dist_dir = paths.web().dist();
let mut copied = 0u64;
let mut failed = 0u64;
for (ext_id, asset) in assets {
match copy_asset(dist_dir, ext_id, &asset).await {
Ok(()) => copied += 1,
Err(e) => {
if asset.is_required() {
return Err(e);
}
tracing::warn!(
extension = %ext_id,
asset = %asset.source().display(),
error = %e,
"Optional asset copy failed"
);
failed += 1;
},
}
}
let duration_ms = start_time.elapsed().as_millis() as u64;
tracing::info!(
copied,
failed,
duration_ms,
"Copy extension assets job completed"
);
Ok(JobResult::success()
.with_stats(copied, failed)
.with_duration(duration_ms))
}
async fn copy_asset(dist_dir: &Path, ext_id: &str, asset: &AssetDefinition) -> Result<()> {
let dest_path = dist_dir.join(asset.destination());
if let Some(parent) = dest_path.parent() {
tokio::fs::create_dir_all(parent).await.map_err(|e| {
PublishError::other(format!(
"Failed to create directory {}: {e}",
parent.display()
))
})?;
}
tokio::fs::copy(asset.source(), &dest_path)
.await
.map_err(|e| {
PublishError::other(format!(
"Failed to copy asset from {} to {}: {e}",
asset.source().display(),
dest_path.display()
))
})?;
tracing::debug!(
extension = %ext_id,
source = %asset.source().display(),
destination = %dest_path.display(),
"Copied extension asset"
);
Ok(())
}