import AppKit import Darwin import Foundation import WebKit private func logSmoke(_ message: String) { fputs("[JSX-SMOKE] \(message)\n", stderr) fflush(stderr) } final class JSXSwiftSmokeApp: NSObject { private let origin: String private let destination: String private let date: String private let useService: Bool private let flightNumber: String private let departureTime: String? var exitCode: Int32 = 1 init( origin: String, destination: String, date: String, useService: Bool, flightNumber: String, departureTime: String? ) { self.origin = origin self.destination = destination self.date = date self.useService = useService self.flightNumber = flightNumber self.departureTime = departureTime } private func parsedDate() -> Date? { let formatter = DateFormatter() formatter.locale = Locale(identifier: "en_US_POSIX") formatter.timeZone = TimeZone(secondsFromGMT: 0) formatter.dateFormat = "yyyy-MM-dd" return formatter.date(from: date) } private func writeSummary(_ summary: [String: Any]) { if JSONSerialization.isValidJSONObject(summary), let data = try? JSONSerialization.data(withJSONObject: summary, options: [.prettyPrinted, .sortedKeys]), let text = String(data: data, encoding: .utf8) { print(text) } else { print("Failed to encode summary") } } @MainActor private func runFetcherMode() async { logSmoke("starting fetcher \(origin)->\(destination) on \(date)") let fetcher = JSXWebViewFetcher() let result = await fetcher.fetchAvailability( origin: origin, destination: destination, date: date ) logSmoke("fetcher returned") let flights = result.flights.map { flight in [ "flightNumber": flight.flightNumber, "origin": flight.origin, "destination": flight.destination, "departureLocal": flight.departureLocal, "arrivalLocal": flight.arrivalLocal, "stops": flight.stops, "equipmentType": flight.equipmentType ?? "", "totalAvailable": flight.totalAvailable, "lowestFareTotal": flight.lowestFareTotal ?? NSNull(), "classes": flight.classes.map { fareClass in [ "classOfService": fareClass.classOfService, "productClass": fareClass.productClass, "availableCount": fareClass.availableCount, "fareTotal": fareClass.fareTotal, "revenueTotal": fareClass.revenueTotal, "fareBasisCode": fareClass.fareBasisCode ?? NSNull() ] } ] as [String: Any] } var summary: [String: Any] = [ "mode": "fetcher", "origin": origin, "destination": destination, "date": date, "flightCount": result.flights.count, "rawSearchBodyLength": result.rawSearchBody?.count ?? 0, "error": result.error ?? NSNull(), "flights": flights ] if let lowFare = result.lowFareFallback { summary["lowFareFallback"] = [ "date": lowFare.date, "available": lowFare.available, "lowestPrice": lowFare.lowestPrice as Any? ?? NSNull() ] } writeSummary(summary) // Verification requires true per-flight data, not only the route-level fallback. exitCode = (result.error == nil && !result.flights.isEmpty) ? 0 : 1 } @MainActor private func runServiceMode() async { logSmoke("starting service \(flightNumber) \(origin)->\(destination) on \(date)" + (departureTime.map { " @ \($0)" } ?? "")) guard let queryDate = parsedDate() else { writeSummary([ "mode": "service", "error": "invalid date \(date)" ]) exitCode = 1 return } let service = AirlineLoadService() let result = await service.fetchLoad( airlineCode: "XE", flightNumber: flightNumber, date: queryDate, origin: origin, destination: destination, departureTime: departureTime ) logSmoke("service returned") let cabins = result?.cabins.map { cabin in [ "name": cabin.name, "capacity": cabin.capacity, "booked": cabin.booked, "available": cabin.available, "revenueStandby": cabin.revenueStandby, "nonRevStandby": cabin.nonRevStandby, "waitListCount": cabin.waitListCount, "jumpSeat": cabin.jumpSeat ] } ?? [] let summary: [String: Any] = [ "mode": "service", "queryFlightNumber": flightNumber, "queryDepartureTime": departureTime ?? NSNull(), "origin": origin, "destination": destination, "date": date, "result": result.map { load in [ "airlineCode": load.airlineCode, "flightNumber": load.flightNumber, "totalAvailable": load.totalAvailable, "totalCapacity": load.totalCapacity, "cabins": cabins ] as [String: Any] } ?? NSNull() ] writeSummary(summary) let isPerFlightCabin = cabins.contains { ($0["name"] as? String) == "Cabin" } exitCode = (result != nil && isPerFlightCabin) ? 0 : 1 } @MainActor func start() async { if useService { await runServiceMode() } else { await runFetcherMode() } NSApp.terminate(nil) } } @main struct JSXSwiftSmokeMain { static func main() { let env = ProcessInfo.processInfo.environment let origin = (env["JSX_ORIGIN"] ?? "DAL").uppercased() let destination = (env["JSX_DESTINATION"] ?? "HOU").uppercased() let date = env["JSX_DATE"] ?? "2026-04-15" let useService = env["JSX_USE_SERVICE"] == "1" let flightNumber = env["JSX_FLIGHT_NUMBER"] ?? "XE280" let departureTime = env["JSX_DEPARTURE_TIME"] let app = NSApplication.shared let delegate = JSXSwiftSmokeApp( origin: origin, destination: destination, date: date, useService: useService, flightNumber: flightNumber, departureTime: departureTime ) logSmoke("booting app") app.setActivationPolicy(.prohibited) Timer.scheduledTimer(withTimeInterval: 120, repeats: false) { _ in logSmoke("timeout after 120s") NSApp.terminate(nil) } Task { @MainActor in await delegate.start() } app.run() exit(delegate.exitCode) } }