Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 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