import ArgumentParser import Foundation struct UploadDSYMs: AsyncParsableCommand { static let configuration = CommandConfiguration(commandName: "upload-dsyms", abstract: "Uploads dSYMs to Sentry using sentry-cli.", discussion: "Requires the SENTRY_AUTH_TOKEN environment variable to be set.") @Option(help: "The path to the dSYMs directory or file to upload.") var dsymPath: String @Option(help: "The Sentry organization slug.") var orgSlug = "element" @Option(help: "The Sentry project slug.") var projectSlug = "element-x-ios" @Option(help: "The Sentry server URL.") var url = "https://sentry.tools.element.io/" @Option(help: "The maximum number of upload attempts.") var maxRetries = 5 func run() async throws { guard let authToken = ProcessInfo.processInfo.environment["SENTRY_AUTH_TOKEN"], !authToken.isEmpty else { throw ValidationError("SENTRY_AUTH_TOKEN environment variable is not set.") } let command = """ sentry-cli --url "\(url)" dif upload \ --auth-token "\(authToken)" \ --org "\(orgSlug)" \ --project "\(projectSlug)" \ --log-level debug \ "\(dsymPath)" """ var lastError: Swift.Error? for attempt in 1...maxRetries { do { logger.info("\n📡 Uploading dSYMs to Sentry (attempt \(attempt)/\(maxRetries))…\n") try await CI.run(.path("/bin/zsh"), ["-cu", command]) logger.info("\n✅ Successfully uploaded dSYMs to Sentry.\n") return } catch { lastError = error logger.error("\n❌ Sentry upload attempt \(attempt) failed: \(error.localizedDescription)\n") } } if let lastError { throw lastError } } }