Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions app/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ clap = { version = "4", features = ["derive"] }
# Web server
axum = { version = "0.8", features = ["ws"] }
tokio = { version = "1", features = ["full"] }
tokio-stream = "0.1"
futures = "0.3"
async-stream = "0.3"

# Serialization
serde = { version = "1", features = ["derive"] }
Expand Down
8 changes: 7 additions & 1 deletion app/src/routes/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,13 @@ pub fn create_routes(frontend_dir: Option<&str>) -> Router<Arc<AppState>> {
.route("/about", get(health::about))
.route("/projects/validate", post(projects::validate_project))
.route("/projects/recent", get(projects::get_recent_projects))
.route("/xcode/discover", post(xcode::discover_project));
.route("/xcode/discover", post(xcode::discover_project))
.route("/xcode/build", post(xcode::build_scheme))
.route("/xcode/build/stream", post(xcode::build_scheme_stream))
.route(
"/xcode/launchable-products",
post(xcode::get_launchable_products),
);

let router = Router::new().nest("/api", api_routes);

Expand Down
111 changes: 107 additions & 4 deletions app/src/routes/xcode.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
use crate::xcode;
use axum::{http::StatusCode, response::IntoResponse, Json};
use axum::{
http::StatusCode,
response::{
sse::{Event, KeepAlive, Sse},
IntoResponse,
},
Json,
};
use futures::stream::StreamExt;
use serde::Deserialize;
use serde_json::json;
use std::path::Path;
Expand All @@ -9,18 +17,113 @@ pub struct DiscoverProjectRequest {
pub path: String,
}

#[derive(Debug, Deserialize)]
pub struct BuildSchemeRequest {
pub path: String,
pub scheme: String,
}

#[derive(Debug, Deserialize)]
pub struct GetLaunchableProductsRequest {
pub build_dir: String,
}

/// Discover Xcode project information (schemes, targets, configurations)
pub async fn discover_project(Json(request): Json<DiscoverProjectRequest>) -> impl IntoResponse {
let path = Path::new(&request.path);

match xcode::discover_project(path).await {
Ok(project) => {
(StatusCode::OK, Json(serde_json::to_value(project).unwrap())).into_response()
}
Ok(project) => match serde_json::to_value(project) {
Ok(json) => (StatusCode::OK, Json(json)).into_response(),
Err(e) => (
StatusCode::INTERNAL_SERVER_ERROR,
Json(json!({ "error": format!("Failed to serialize response: {}", e) })),
)
.into_response(),
},
Err(error) => (
StatusCode::BAD_REQUEST,
Json(json!({ "error": error.to_string() })),
)
.into_response(),
}
}

/// Build an Xcode scheme for iOS Simulator with code signing disabled
pub async fn build_scheme(Json(request): Json<BuildSchemeRequest>) -> impl IntoResponse {
let path = Path::new(&request.path);

match xcode::build_scheme(path, &request.scheme).await {
Ok(result) => match serde_json::to_value(result) {
Ok(json) => (StatusCode::OK, Json(json)).into_response(),
Err(e) => (
StatusCode::INTERNAL_SERVER_ERROR,
Json(json!({ "error": format!("Failed to serialize response: {}", e) })),
)
.into_response(),
},
Err(error) => (
StatusCode::BAD_REQUEST,
Json(json!({ "error": error.to_string() })),
)
.into_response(),
}
}

/// Get launchable products from a build directory
pub async fn get_launchable_products(
Json(request): Json<GetLaunchableProductsRequest>,
) -> impl IntoResponse {
match xcode::get_launchable_products_from_dir(&request.build_dir).await {
Ok(products) => match serde_json::to_value(products) {
Ok(json) => (StatusCode::OK, Json(json)).into_response(),
Err(e) => (
StatusCode::INTERNAL_SERVER_ERROR,
Json(json!({ "error": format!("Failed to serialize response: {}", e) })),
)
.into_response(),
},
Err(error) => (
StatusCode::BAD_REQUEST,
Json(json!({ "error": error.to_string() })),
)
.into_response(),
}
}

/// Stream build output via Server-Sent Events
pub async fn build_scheme_stream(
Json(request): Json<BuildSchemeRequest>,
) -> Result<
Sse<impl futures::Stream<Item = Result<Event, std::convert::Infallible>>>,
(StatusCode, Json<serde_json::Value>),
> {
let path = Path::new(&request.path);

let event_stream = match xcode::build_scheme_stream(path, &request.scheme).await {
Ok(stream) => stream,
Err(error) => {
return Err((
StatusCode::BAD_REQUEST,
Json(json!({ "error": error.to_string() })),
));
}
};

let sse_stream = event_stream.map(|result| match result {
Ok(event) => {
let json_data = serde_json::to_string(&event).unwrap_or_else(|e| {
tracing::error!("Failed to serialize build event: {}", e);
json!({"type": "error", "message": "Failed to serialize event"}).to_string()
});
Ok(Event::default().data(json_data))
}
Err(e) => {
tracing::error!("Build stream error: {}", e);
let error_json = json!({"type": "error", "message": "Stream error"}).to_string();
Ok(Event::default().data(error_json))
}
});

Ok(Sse::new(sse_stream).keep_alive(KeepAlive::default()))
}
Loading