import Foundation // MARK: - Result type (Codable so /system/validate can return JSON) struct StartupCheckResult: Codable { let name: String let passed: Bool let message: String } // MARK: - Validator enum DaemonStartupValidator { static func run() -> [StartupCheckResult] { [ checkXcode(), checkXcodebuild(), checkDevicectl(), checkSourcekitLSP(), checkTeamID(), checkSigningIdentity(), checkXcodeGen(), ] } // MARK: - Checks private static func checkXcode() -> StartupCheckResult { let exists = FileManager.default.fileExists(atPath: "/Applications/Xcode.app") return StartupCheckResult( name: "Xcode Installation", passed: exists, message: exists ? "Xcode found at /Applications/Xcode.app" : "Xcode not found — install from developer.apple.com or the App Store" ) } private static func checkXcodebuild() -> StartupCheckResult { let result = shell("xcodebuild -version 2>/dev/null | head -1") let passed = result.contains("Xcode") return StartupCheckResult( name: "xcodebuild", passed: passed, message: passed ? result.trimmingCharacters(in: .whitespacesAndNewlines) : "xcodebuild not functional — run: xcode-select --install" ) } private static func checkDevicectl() -> StartupCheckResult { let result = shell("xcrun devicectl --version 2>/dev/null") let passed = !result.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty return StartupCheckResult( name: "devicectl", passed: passed, message: passed ? "devicectl available" : "devicectl not found — requires Xcode 15+" ) } private static func checkSourcekitLSP() -> StartupCheckResult { let path = "/Applications/Xcode.app/Contents/Developer/Toolchains/" + "XcodeDefault.xctoolchain/usr/bin/sourcekit-lsp" let exists = FileManager.default.fileExists(atPath: path) return StartupCheckResult( name: "sourcekit-lsp", passed: exists, message: exists ? "sourcekit-lsp found" : "sourcekit-lsp not found — completions unavailable" ) } private static func checkTeamID() -> StartupCheckResult { let teamID = loadTeamID() let valid = teamID.count == 10 && teamID != "YOUR_10_CHAR_TEAM_ID" && teamID.allSatisfy({ $0.isLetter || $0.isNumber }) return StartupCheckResult( name: "Team ID", passed: valid, message: valid ? "Team ID configured: \(teamID)" : "Team ID not set — edit ~/.padxcode/config.json" ) } private static func checkSigningIdentity() -> StartupCheckResult { let result = shell( "security find-identity -v -p codesigning 2>/dev/null | grep 'Apple Development'" ) let passed = !result.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty let certName = result .components(separatedBy: "\"") .dropFirst() .first ?? "certificate present" return StartupCheckResult( name: "Apple Development Certificate", passed: passed, message: passed ? "Found: \(certName)" : "No Apple Development certificate — Xcode → Settings → Accounts → Manage Certificates" ) } private static func checkXcodeGen() -> StartupCheckResult { let result = shell("which xcodegen 2>/dev/null") .trimmingCharacters(in: .whitespacesAndNewlines) let passed = !result.isEmpty return StartupCheckResult( name: "XcodeGen", passed: passed, message: passed ? "XcodeGen at \(result)" : "XcodeGen not found — brew install xcodegen" ) } // MARK: - Shell helper @discardableResult private static func shell(_ command: String) -> String { let p = Process() p.executableURL = URL(fileURLWithPath: "/bin/zsh") p.arguments = ["-lc", command] let pipe = Pipe() p.standardOutput = pipe p.standardError = Pipe() try? p.run() p.waitUntilExit() return String( data: pipe.fileHandleForReading.readDataToEndOfFile(), encoding: .utf8 ) ?? "" } }