#!/usr/bin/swift sh import func Darwin.fputs import var Darwin.stderr import PMKFoundation // PromiseKit/Foundation ~> 3.3 import LegibleError // @mxcl ~> 1.0 import Foundation import PromiseKit // @mxcl ~> 6.8 import Path // mxcl/Path.swift == master let env = ProcessInfo.processInfo.environment let token = env["GITHUB_TOKEN"] ?? env["GITHUB_ACCESS_TOKEN"]! let slug = env["TRAVIS_REPO_SLUG"]! let tag = env["TRAVIS_TAG"]! func fatal(message: String) -> Never { fputs("error: \(message)\n", stderr) exit(1) } func fatal(error: Error) -> Never { fatal(message: error.legibleLocalizedDescription) } guard let licenseFile = try Path.cwd.ls().files.first(where: { $0.basename().hasPrefix("LICENSE") })?.basename() else { fatal(message: "no LICENSE file found") } struct Repo: Decodable { let description: String let license: License struct License: Decodable { let spdx_id: String } } struct Package: Decodable { let swiftLanguageVersions: [String] let targets: [Target] struct Target: Decodable { let path: String? let type: Kind enum Kind: String, Decodable { case regular case test } } } extension URLRequest { init(github path: String) { let url = URL(string: "https://api.github.com\(path)")! self.init(url: url) setValue("token \(token)", forHTTPHeaderField: "Authorization") setValue("application/json", forHTTPHeaderField: "Content-Type") setValue("application/json", forHTTPHeaderField: "Accept") } } func description() -> Promise { let rq = URLRequest(github: "/repos/\(slug)") return firstly { URLSession.shared.dataTask(.promise, with: rq).validate() }.map { data, _ in try JSONDecoder().decode(Repo.self, from: data) } } struct User: Decodable { let name: String let email: String } func email() -> Promise { let rq = URLRequest(github: "/user") return firstly { URLSession.shared.dataTask(.promise, with: rq).validate() }.map { data, _ in try JSONDecoder().decode(User.self, from: data) } } func dumpPackage() -> Promise { let task = Process() task.launchPath = "/usr/bin/swift" task.arguments = ["package", "dump-package"] return firstly { task.launch(.promise) }.map { out, _ in out.fileHandleForReading.readDataToEndOfFile() }.map { data in try JSONDecoder().decode(Package.self, from: data) } } var defaultSwiftVersion: String { let task = Process() task.launchPath = "/usr/bin/swift" task.arguments = ["--version"] func extract(input: String) -> String { let range = input.range(of: #"Apple Swift version \d+\.\d+"#, options: .regularExpression)! return String(input[range].split(separator: " ").last!) } return try! firstly { task.launch(.promise) }.compactMap { out, _ in String(data: out.fileHandleForReading.readDataToEndOfFile(), encoding: .utf8) }.map { out in extract(input: out) }.wait() } func podspec(repo: Repo, user: User, pkg: Package) -> (Substring, String) { let (owner, name) = { ($0[0], $0[1]) }(slug.split(separator: "/")) let swiftVersion = pkg.swiftLanguageVersions.min() ?? defaultSwiftVersion let targets = pkg.targets.filter{ $0.type == .regular } guard targets.count == 1 else { fatal(message: "Too many targets for this script!") } guard let sources = targets[0].path else { fatal(message: "Target has no path!") } return (name, """ Pod::Spec.new do |s| s.name = '\(name)' s.version = '\(tag)' s.summary = '\(repo.description)' s.homepage = "https://github.com/\(slug)" s.license = { type: '\(repo.license.spdx_id)', file: '\(licenseFile)' } s.author = { '\(user.name)': '\(user.email)' } s.source = { git: "https://github.com/\(slug).git", tag: '\(tag)' } s.social_media_url = 'https://twitter.com/\(owner)' s.osx.deployment_target = '10.10' s.ios.deployment_target = '8.0' s.tvos.deployment_target = '9.0' s.watchos.deployment_target = '2.0' s.source_files = '\(sources)/*.swift' s.swift_version = '\(swiftVersion)' end """) } func publishRelease() throws -> Promise { struct Input: Encodable { var tag_name: String { return tag } var name: String { return tag } let body = "" } var rq = URLRequest(github: "/repos/\(slug)/releases") rq.httpMethod = "POST" rq.httpBody = try JSONEncoder().encode(Input()) return URLSession.shared.dataTask(.promise, with: rq).validate().asVoid() } switch CommandLine.arguments[1] { case "generate-podspec": firstly { when(fulfilled: description(), email(), dumpPackage()) }.map(podspec).done { name, podspec in try podspec.write(toFile: "\(name).podspec", atomically: false, encoding: .utf8) exit(0) }.catch { fatal(error: $0) } case "publish-release": try publishRelease().done { exit(0) }.catch { fatal(error: $0) } default: fatal(message: "invalid usage") } RunLoop.main.run()