Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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
80 changes: 79 additions & 1 deletion app/src/routes/xcode.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
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,6 +14,17 @@ 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);
Expand All @@ -24,3 +40,65 @@ pub async fn discover_project(Json(request): Json<DiscoverProjectRequest>) -> im
.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) => (StatusCode::OK, Json(serde_json::to_value(result).unwrap())).into_response(),
Copy link

Copilot AI Dec 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The unwrap() call will panic if serialization fails. While serialization of the BuildResult is unlikely to fail, for consistency with error handling patterns in the rest of the code, consider using map_err to convert to a 500 Internal Server Error, or at least use expect() with a descriptive message.

Suggested change
Ok(result) => (StatusCode::OK, Json(serde_json::to_value(result).unwrap())).into_response(),
Ok(result) => match serde_json::to_value(result) {
Ok(value) => (StatusCode::OK, Json(value)).into_response(),
Err(error) => (
StatusCode::INTERNAL_SERVER_ERROR,
Json(json!({ "error": error.to_string() })),
)
.into_response(),
},

Copilot uses AI. Check for mistakes.
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) => {
(StatusCode::OK, Json(serde_json::to_value(products).unwrap())).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(|_| "{}".to_string());
Ok(Event::default().data(json_data))
}
Err(_) => {
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
Loading