├── .gitignore ├── .swiftlint.yml ├── BuildaGitServer ├── Base │ ├── Authentication.swift │ ├── BaseTypes.swift │ ├── GitServerFactory.swift │ └── SourceServerExtensions.swift ├── BuildaGitServer.h ├── Extensions.swift ├── GitHub │ ├── GitHubBranch.swift │ ├── GitHubComment.swift │ ├── GitHubCommit.swift │ ├── GitHubEndpoints.swift │ ├── GitHubEntity.swift │ ├── GitHubIssue.swift │ ├── GitHubPullRequest.swift │ ├── GitHubPullRequestBranch.swift │ ├── GitHubRateLimit.swift │ ├── GitHubRepo.swift │ ├── GitHubServer.swift │ ├── GitHubStatus.swift │ └── GitHubUser.swift ├── GitServerPublic.swift ├── Info.plist └── Slack │ └── SlackNotifier.swift ├── BuildaGitServerTests ├── GitHubServerTests.swift └── Info.plist ├── BuildaHeartbeatKit ├── BuildaHeartbeatKit.h ├── Heartbeat.swift └── Info.plist ├── BuildaKit ├── Availability.swift ├── BlueprintFileParser.swift ├── BuildTemplate.swift ├── BuildaKit.h ├── CheckoutFileParser.swift ├── CommonExtensions.swift ├── ConfigTriplet.swift ├── GitRepoMetadataParser.swift ├── Info.plist ├── Logging.swift ├── LoginItem.swift ├── NetworkUtils.swift ├── Persistence.swift ├── PersistenceMigrator.swift ├── Project.swift ├── ProjectConfig.swift ├── SSHKeyVerification.swift ├── SecurePersistence.swift ├── SourceControlFileParser.swift ├── StandardSyncer.swift ├── StorageManager.swift ├── StorageUtils.swift ├── SummaryBuilder.swift ├── SyncPair.swift ├── SyncPairBranchResolver.swift ├── SyncPairExtensions.swift ├── SyncPairPRResolver.swift ├── SyncPairResolver.swift ├── SyncPair_Branch_Bot.swift ├── SyncPair_Branch_NoBot.swift ├── SyncPair_Deletable_Bot.swift ├── SyncPair_PR_Bot.swift ├── SyncPair_PR_NoBot.swift ├── Syncer.swift ├── SyncerBotManipulation.swift ├── SyncerBotNaming.swift ├── SyncerConfig.swift ├── SyncerFactory.swift ├── SyncerLogic.swift ├── SyncerManager.swift ├── SyncerNotifierUtils.swift ├── SyncerProducerFactory.swift ├── TriggerConfig.swift ├── WorkspaceMetadata.swift ├── XcodeDeviceParser.swift ├── XcodeProject.swift ├── XcodeProjectParser.swift ├── XcodeProjectXMLParser.swift ├── XcodeScheme.swift └── XcodeServerSyncerUtils.swift ├── BuildaKitTests ├── ExtensionTests.swift ├── GeneralTests.swift ├── GitHubSummaryBuilderTests.swift ├── Info.plist ├── Migration │ ├── Buildasaur-format-0-example1 │ │ ├── BuildTemplates │ │ │ └── B31C6530-BB84-4A93-B08C-54074AAB5F37.json │ │ ├── Builda.log │ │ ├── Config.json │ │ ├── Projects.json │ │ ├── ServerConfigs.json │ │ └── Syncers.json │ ├── Buildasaur-format-1-example1 │ │ ├── BuildTemplates │ │ │ └── B31C6530-BB84-4A93-B08C-54074AAB5F37.json │ │ ├── Builda.log │ │ ├── Config.json │ │ ├── Projects.json │ │ ├── ServerConfigs.json │ │ └── Syncers.json │ ├── Buildasaur-format-2-example1 │ │ ├── BuildTemplates │ │ │ └── B31C6530-BB84-4A93-B08C-54074AAB5F37.json │ │ ├── Builda.log │ │ ├── Config.json │ │ ├── Projects.json │ │ ├── ServerConfigs.json │ │ ├── Syncers.json │ │ └── Triggers │ │ │ ├── 4E66D0D5-D5CC-417E-A40E-73B513CE4E10.json │ │ │ └── E8F5285A-A262-4630-AF7B-236772B75760.json │ ├── Buildasaur-format-2-example2 │ │ ├── BuildTemplates │ │ │ ├── 9B53CC35-57DA-4DA0-9B85-05FCF109512A.json │ │ │ └── B31C6530-BB84-4A93-B08C-54074AAB5F37.json │ │ ├── Builda.log │ │ ├── Config.json │ │ ├── Projects.json │ │ ├── ServerConfigs.json │ │ ├── Syncers.json │ │ └── Triggers │ │ │ ├── 4E66D0D5-D5CC-417E-A40E-73B513CE4E10.json │ │ │ └── E8F5285A-A262-4630-AF7B-236772B75760.json │ ├── Buildasaur-format-3-example1 │ │ ├── BuildTemplates │ │ │ ├── 9B53CC35-57DA-4DA0-9B85-05FCF109512A.json │ │ │ └── B31C6530-BB84-4A93-B08C-54074AAB5F37.json │ │ ├── Config.json │ │ ├── Logs │ │ │ └── Builda.log │ │ ├── Projects.json │ │ ├── ServerConfigs.json │ │ ├── Syncers.json │ │ └── Triggers │ │ │ ├── 4E66D0D5-D5CC-417E-A40E-73B513CE4E10.json │ │ │ └── E8F5285A-A262-4630-AF7B-236772B75760.json │ └── Buildasaur-format-4-example1 │ │ ├── BuildTemplates │ │ ├── 9B53CC35-57DA-4DA0-9B85-05FCF109512A.json │ │ └── B31C6530-BB84-4A93-B08C-54074AAB5F37.json │ │ ├── Config.json │ │ ├── Logs │ │ └── Builda.log │ │ ├── Projects.json │ │ ├── ServerConfigs.json │ │ ├── Syncers.json │ │ └── Triggers │ │ ├── 4E66D0D5-D5CC-417E-A40E-73B513CE4E10.json │ │ └── E8F5285A-A262-4630-AF7B-236772B75760.json ├── MigrationTests.swift ├── MockHelpers.swift ├── Mocks.swift ├── SyncPair_PR_Bot_Tests.swift ├── SyncerTests.swift ├── TestProjects │ └── Buildasaur-TestProject-iOS │ │ ├── Buildasaur-TestProject-iOS.xcodeproj │ │ ├── project.pbxproj │ │ ├── project.xcworkspace │ │ │ └── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── Buildasaur-TestProject-iOS.xcscmblueprint │ │ │ └── xcschemes │ │ │ └── Buildasaur-TestProject-iOS.xcscheme │ │ ├── Buildasaur-TestProject-iOS.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── Buildasaur-TestProject-iOS.xcscmblueprint │ │ └── Buildasaur-TestProject-iOS │ │ ├── AppDelegate.swift │ │ ├── Assets.xcassets │ │ └── AppIcon.appiconset │ │ │ └── Contents.json │ │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ │ ├── Info.plist │ │ └── ViewController.swift ├── WorkspaceMetadataTests.swift └── sampleFinishedIntegration.json ├── Buildasaur.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ └── contents.xcworkspacedata └── xcshareddata │ └── xcschemes │ ├── BuildaKit.xcscheme │ └── Buildasaur.xcscheme ├── Buildasaur.xcworkspace ├── contents.xcworkspacedata └── xcshareddata │ ├── Buildasaur.xccheckout │ └── Buildasaur.xcscmblueprint ├── Buildasaur ├── AppDelegate.swift ├── Authentication │ └── ServiceAuthentication.swift ├── Base.lproj │ └── Main.storyboard ├── Buildasaur.entitlements ├── Images.xcassets │ ├── AppIcon.appiconset │ │ ├── Contents.json │ │ ├── Icon_128.png │ │ ├── Icon_128@2x.png │ │ ├── Icon_16.png │ │ ├── Icon_16@2x.png │ │ ├── Icon_256.png │ │ ├── Icon_256@2x.png │ │ ├── Icon_32.png │ │ ├── Icon_32@2x.png │ │ ├── Icon_512.png │ │ └── Icon_512@2x.png │ ├── Contents.json │ ├── builda.imageset │ │ ├── Contents.json │ │ ├── builda1.png │ │ ├── builda2.png │ │ └── builda3.png │ └── icon.imageset │ │ ├── Contents.json │ │ ├── Icon_mono_16.png │ │ ├── Icon_mono_16@2x.png │ │ └── Icon_mono_16@3x.png ├── Info.plist ├── Utils │ ├── NSButton+OnClick.swift │ ├── NSStepper+ValueChanged.swift │ ├── StoryboardLoadingUtils.swift │ └── URLUtils.swift ├── ViewControllers │ ├── Base │ │ ├── ConfigEditViewController.swift │ │ └── PresentableViewController.swift │ ├── Dashboard │ │ ├── DashboardViewController.swift │ │ └── MenuItemManager.swift │ ├── Editables │ │ ├── BranchWatchingViewController.swift │ │ ├── BuildTemplateViewController.swift │ │ ├── EmptyBuildTemplateViewController.swift │ │ ├── EmptyProjectViewController.swift │ │ ├── EmptyXcodeServerViewController.swift │ │ ├── ManualBotManagementViewController.swift │ │ ├── ProjectViewController.swift │ │ ├── SelectTriggerViewController.swift │ │ ├── SyncerViewController.swift │ │ ├── TriggerViewController.swift │ │ └── XcodeServerViewController.swift │ └── Editor │ │ ├── EditableViewController.swift │ │ ├── EditorContext.swift │ │ ├── EditorState.swift │ │ ├── EditorViewControllerFactory.swift │ │ ├── MainEditorViewController.swift │ │ ├── MainEditor_EditeeDelegate.swift │ │ └── MainEditor_ViewManipulation.swift ├── ViewModels │ └── SyncerViewModel.swift ├── Views │ ├── SeparatorView.swift │ └── UIUtils.swift └── launch_item.plist ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── LICENSE ├── Meta ├── bitbucket.png ├── builda_screenshot.png ├── comment.png ├── github.png └── menu_bar.png ├── Podfile ├── Podfile.lock ├── README.md ├── fastlane ├── Fastfile ├── README.md └── actions │ ├── render_github_markdown.rb │ └── sparkle_add_update.rb └── sparkle.xml /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | 3 | # Xcode 4 | # 5 | build/ 6 | gh-pages/ 7 | *.pbxuser 8 | !default.pbxuser 9 | *.mode1v3 10 | !default.mode1v3 11 | *.mode2v3 12 | !default.mode2v3 13 | *.perspectivev3 14 | !default.perspectivev3 15 | xcuserdata 16 | *.moved-aside 17 | DerivedData 18 | *.hmap 19 | *.ipa 20 | *.xcuserstate 21 | 22 | **/report.xml 23 | fastlane/test_output/ 24 | 25 | # CocoaPods 26 | # 27 | # We recommend against adding the Pods directory to your .gitignore. However 28 | # you should judge for yourself, the pros and cons are mentioned at: 29 | # http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control 30 | # 31 | Pods/ 32 | Podfile.local 33 | -------------------------------------------------------------------------------- /.swiftlint.yml: -------------------------------------------------------------------------------- 1 | disabled_rules: 2 | - file_header 3 | - file_length 4 | - function_body_length 5 | - function_parameter_count 6 | - identifier_name 7 | - large_tuple 8 | - line_length 9 | - type_body_length 10 | - type_name 11 | - force_cast 12 | 13 | opt_in_rules: 14 | - closure_spacing 15 | - empty_count 16 | - explicit_init 17 | - file_header 18 | - first_where 19 | - object_literal 20 | - operator_usage_whitespace 21 | - overridden_super_call 22 | - pattern_matching_keywords 23 | - prohibited_super_call 24 | - redundant_nil_coalescing 25 | - vertical_parameter_alignment_on_call 26 | - void_return 27 | # Find all the available rules by running: 28 | # swiftlint rules 29 | 30 | excluded: 31 | - Pods 32 | - BuildaKitTests 33 | - BuildaGitServerTests 34 | 35 | force_cast: warning 36 | reporter: "xcode" # reporter type (xcode, json, csv, checkstyle) 37 | -------------------------------------------------------------------------------- /BuildaGitServer/Base/Authentication.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Authentication.swift 3 | // Buildasaur 4 | // 5 | // Created by Honza Dvorsky on 1/26/16. 6 | // Copyright © 2016 Honza Dvorsky. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import BuildaUtils 11 | 12 | public struct ProjectAuthenticator { 13 | public enum AuthType: String { 14 | case PersonalToken 15 | case OAuthToken 16 | } 17 | 18 | public let service: GitService 19 | public let username: String 20 | public let type: AuthType 21 | public let secret: String 22 | 23 | public init(service: GitService, username: String, type: AuthType, secret: String) { 24 | self.service = service 25 | self.username = username 26 | self.type = type 27 | self.secret = secret 28 | } 29 | } 30 | 31 | public protocol KeychainStringSerializable { 32 | static func fromString(value: String) throws -> Self 33 | func toString() -> String 34 | } 35 | 36 | extension ProjectAuthenticator: KeychainStringSerializable { 37 | public static func fromString(value: String) throws -> ProjectAuthenticator { 38 | let comps = value.components(separatedBy: ":") 39 | guard comps.count >= 4 else { throw GithubServerError.with("Corrupted keychain string") } 40 | guard let service = GitService(rawValue: comps[0]) else { 41 | throw GithubServerError.with("Unsupported service: \(comps[0])") 42 | } 43 | guard let type = ProjectAuthenticator.AuthType(rawValue: comps[2]) else { 44 | throw GithubServerError.with("Unsupported auth type: \(comps[2])") 45 | } 46 | //join the rest back in case we have ":" in the token 47 | let remaining = comps.dropFirst(3).joined(separator: ":") 48 | let auth = ProjectAuthenticator(service: service, username: comps[1], type: type, secret: remaining) 49 | return auth 50 | } 51 | 52 | public func toString() -> String { 53 | return [ 54 | self.service.rawValue, 55 | self.username, 56 | self.type.rawValue, 57 | self.secret 58 | ].joined(separator: ":") 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /BuildaGitServer/Base/GitServerFactory.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GitServerFactory.swift 3 | // Buildasaur 4 | // 5 | // Created by Honza Dvorsky on 13/12/2014. 6 | // Copyright (c) 2014 Honza Dvorsky. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import BuildaUtils 11 | 12 | class GitServerFactory { 13 | 14 | class func server(service: GitService, auth: ProjectAuthenticator?, http: HTTP? = nil) -> SourceServerType & Notifier { 15 | 16 | let server: SourceServerType & Notifier 17 | 18 | switch service { 19 | case .GitHub: 20 | let baseURL = "https://api.github.com" 21 | let endpoints = GitHubEndpoints(baseURL: baseURL, auth: auth) 22 | server = GitHubServer(endpoints: endpoints, http: http) 23 | } 24 | 25 | return server 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /BuildaGitServer/Base/SourceServerExtensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SourceServerExtensions.swift 3 | // Buildasaur 4 | // 5 | // Created by Honza Dvorsky on 14/12/2014. 6 | // Copyright (c) 2014 Honza Dvorsky. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import BuildaUtils 11 | 12 | //functions to make working with github easier - utility functions 13 | extension SourceServerType { 14 | 15 | /** 16 | * Get the latest status of a pull request. 17 | */ 18 | func getStatusOfPullRequest(pullRequestNumber: Int, repo: String, completion: @escaping (_ status: StatusType?, _ error: Error?) -> Void) { 19 | 20 | self.getPullRequest(pullRequestNumber: pullRequestNumber, repo: repo) { (pr, error) -> Void in 21 | 22 | if error != nil { 23 | completion(nil, error) 24 | return 25 | } 26 | 27 | if let pr = pr { 28 | //fetched PR, now take its head's sha - that's the commit we care about. 29 | let sha = pr.headName 30 | self.getStatusOfCommit(commit: sha, repo: repo, completion: completion) 31 | } else { 32 | completion(nil, GithubServerError.with("PR is nil and error is nil")) 33 | } 34 | } 35 | } 36 | 37 | //TODO: support paging through all the comments. currently we only fetch the last ~30 comments. 38 | public func findMatchingCommentInIssue(commentsToMatch: [String], issue: Int, repo: String, completion: @escaping (_ foundComments: [CommentType]?, _ error: Error?) -> Void) { 39 | 40 | self.getCommentsOfIssue(issueNumber: issue, repo: repo) { (comments, error) -> Void in 41 | 42 | if error != nil { 43 | completion(nil, error) 44 | return 45 | } 46 | 47 | if let comments = comments { 48 | let filtered = comments.filter { (comment: CommentType) -> Bool in 49 | 50 | let filteredSearch = commentsToMatch.filter { (searchString: String) -> Bool in 51 | if searchString == comment.body { 52 | return true 53 | } 54 | return false 55 | } 56 | return !filteredSearch.isEmpty 57 | } 58 | completion(filtered, nil) 59 | } else { 60 | completion(nil, GithubServerError.with("Nil comments and nil error. Wat?")) 61 | } 62 | } 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /BuildaGitServer/BuildaGitServer.h: -------------------------------------------------------------------------------- 1 | // 2 | // BuildaGitServer.h 3 | // BuildaGitServer 4 | // 5 | // Created by Honza Dvorsky on 13/12/2014. 6 | // Copyright (c) 2014 Honza Dvorsky. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for BuildaGitServer. 12 | FOUNDATION_EXPORT double BuildaGitServerVersionNumber; 13 | 14 | //! Project version string for BuildaGitServer. 15 | FOUNDATION_EXPORT const unsigned char BuildaGitServerVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | -------------------------------------------------------------------------------- /BuildaGitServer/Extensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Extensions.swift 3 | // Buildasaur 4 | // 5 | // Created by Honza Dvorsky on 1/28/16. 6 | // Copyright © 2016 Honza Dvorsky. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension String { 12 | 13 | public func base64String() -> String { 14 | return self 15 | .data(using: String.Encoding.utf8)! 16 | .base64EncodedString() 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /BuildaGitServer/GitHub/GitHubBranch.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GitHubBranch.swift 3 | // Buildasaur 4 | // 5 | // Created by Honza Dvorsky on 13/12/2014. 6 | // Copyright (c) 2014 Honza Dvorsky. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | class GitHubBranch: GitHubEntity { 12 | 13 | let name: String 14 | let commit: GitHubCommit 15 | 16 | required init(json: NSDictionary) throws { 17 | 18 | self.name = try json.stringForKey("name") 19 | self.commit = try GitHubCommit(json: json.dictionaryForKey("commit")) 20 | try super.init(json: json) 21 | } 22 | } 23 | 24 | extension GitHubBranch: BranchType { 25 | 26 | //name (see above) 27 | 28 | var commitSHA: String { 29 | return self.commit.sha 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /BuildaGitServer/GitHub/GitHubComment.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GitHubComment.swift 3 | // Buildasaur 4 | // 5 | // Created by Honza Dvorsky on 13/12/2014. 6 | // Copyright (c) 2014 Honza Dvorsky. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | class GitHubComment: GitHubEntity { 12 | 13 | let body: String 14 | let author: GitHubUser 15 | 16 | required init(json: NSDictionary) throws { 17 | 18 | self.body = try json.stringForKey("body") 19 | self.author = try GitHubUser(json: json.dictionaryForKey("user")) 20 | 21 | try super.init(json: json) 22 | } 23 | } 24 | 25 | extension GitHubComment: CommentType { 26 | 27 | } 28 | -------------------------------------------------------------------------------- /BuildaGitServer/GitHub/GitHubCommit.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GitHubCommit.swift 3 | // Buildasaur 4 | // 5 | // Created by Honza Dvorsky on 13/12/2014. 6 | // Copyright (c) 2014 Honza Dvorsky. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | //GitHub commit in all its glory, with git commit metadata, plus comments, committer, author and parents info 12 | class GitHubCommit: GitHubEntity { 13 | 14 | let sha: String 15 | 16 | required init(json: NSDictionary) throws { 17 | 18 | self.sha = try json.stringForKey("sha") 19 | 20 | try super.init(json: json) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /BuildaGitServer/GitHub/GitHubEntity.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GitHubEntity.swift 3 | // Buildasaur 4 | // 5 | // Created by Honza Dvorsky on 13/12/2014. 6 | // Copyright (c) 2014 Honza Dvorsky. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | protocol GitHubType { 12 | init(json: NSDictionary) throws 13 | } 14 | 15 | class GitHubEntity: GitHubType { 16 | 17 | let htmlUrl: String? 18 | let url: String? 19 | let id: Int? 20 | 21 | //initializer which takes a dictionary and fills in values for recognized keys 22 | required init(json: NSDictionary) throws { 23 | 24 | self.htmlUrl = json.optionalStringForKey("html_url") 25 | self.url = json.optionalStringForKey("url") 26 | self.id = json.optionalIntForKey("id") 27 | } 28 | 29 | init() { 30 | self.htmlUrl = nil 31 | self.url = nil 32 | self.id = nil 33 | } 34 | 35 | func dictionarify() -> NSDictionary { 36 | assertionFailure("Must be overriden by subclasses that wish to dictionarify their data") 37 | return NSDictionary() 38 | } 39 | 40 | class func optional(json: NSDictionary?) throws -> T? { 41 | if let json = json { 42 | return try T(json: json) 43 | } 44 | return nil 45 | } 46 | 47 | } 48 | 49 | //parse an array of dictionaries into an array of parsed entities 50 | func GitHubArray(jsonArray: NSArray!) throws -> [T] where T: GitHubType { 51 | 52 | let array = jsonArray as! [NSDictionary] 53 | let parsed = try array.map { (json: NSDictionary) -> (T) in 54 | return try T(json: json) 55 | } 56 | return parsed 57 | } 58 | -------------------------------------------------------------------------------- /BuildaGitServer/GitHub/GitHubIssue.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GitHubIssue.swift 3 | // Buildasaur 4 | // 5 | // Created by Honza Dvorsky on 13/12/2014. 6 | // Copyright (c) 2014 Honza Dvorsky. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | class GitHubIssue: GitHubEntity { 12 | 13 | let number: Int 14 | let body: String 15 | var title: String 16 | 17 | required init(json: NSDictionary) throws { 18 | self.number = try json.intForKey("number") 19 | self.body = json.optionalStringForKey("body") ?? "" 20 | self.title = try json.stringForKey("title") 21 | try super.init(json: json) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /BuildaGitServer/GitHub/GitHubPullRequest.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GitHubPullRequest.swift 3 | // Buildasaur 4 | // 5 | // Created by Honza Dvorsky on 13/12/2014. 6 | // Copyright (c) 2014 Honza Dvorsky. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | class GitHubPullRequest: GitHubIssue, PullRequestType { 12 | 13 | let head: GitHubPullRequestBranch 14 | let base: GitHubPullRequestBranch 15 | 16 | required init(json: NSDictionary) throws { 17 | 18 | self.head = try GitHubPullRequestBranch(json: json.dictionaryForKey("head")) 19 | self.base = try GitHubPullRequestBranch(json: json.dictionaryForKey("base")) 20 | 21 | try super.init(json: json) 22 | } 23 | 24 | var headName: String { 25 | return self.head.ref 26 | } 27 | 28 | var headCommitSHA: String { 29 | return self.head.sha 30 | } 31 | 32 | var headRepo: RepoType { 33 | return self.head.repo 34 | } 35 | 36 | var baseName: String { 37 | return self.base.ref 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /BuildaGitServer/GitHub/GitHubPullRequestBranch.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GitHubBranch.swift 3 | // Buildasaur 4 | // 5 | // Created by Honza Dvorsky on 13/12/2014. 6 | // Copyright (c) 2014 Honza Dvorsky. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import BuildaUtils 11 | 12 | //PullRequestBranch is a special type of a branch - it also includes repo info (bc PRs can be cross repos) 13 | //normal branches include less information 14 | class GitHubPullRequestBranch: GitHubEntity { 15 | 16 | let ref: String 17 | let sha: String 18 | let repo: GitHubRepo 19 | 20 | required init(json: NSDictionary) throws { 21 | 22 | self.ref = try json.stringForKey("ref") 23 | self.sha = try json.stringForKey("sha") 24 | guard let repo = json.optionalDictionaryForKey("repo") else { 25 | throw GithubServerError.with("PR missing information about its repository") 26 | } 27 | self.repo = try GitHubRepo(json: repo) 28 | 29 | try super.init(json: json) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /BuildaGitServer/GitHub/GitHubRateLimit.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GitHubRateLimit.swift 3 | // Buildasaur 4 | // 5 | // Created by Honza Dvorsky on 03/05/2015. 6 | // Copyright (c) 2015 Honza Dvorsky. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct GitHubRateLimit { 12 | 13 | let resetTime: Double 14 | let limit: Int 15 | let remaining: Int 16 | let now: Double = NSDate().timeIntervalSince1970 17 | 18 | func getReport() -> String { 19 | 20 | let resetInterval = 3600.0 //reset interval is 1 hour 21 | let startTime = self.resetTime - resetInterval 22 | let remainingTime = self.resetTime - self.now 23 | let consumed = self.limit - self.remaining 24 | let consumedTime = self.now - startTime 25 | let rateOfConsumption = Double(consumed) / consumedTime 26 | let rateOfConsumptionPretty = rateOfConsumption.clipTo(2) 27 | let maxRateOfConsumption = Double(self.limit) / resetInterval 28 | let maxRateOfConsumptionPretty = maxRateOfConsumption.clipTo(2) 29 | 30 | //how much faster we can be consuming requests before we hit the maximum rate of 5000/hour 31 | let usedRatePercent = (100.0 * rateOfConsumption / maxRateOfConsumption).clipTo(2) 32 | 33 | let report = "count: \(consumed)/\(self.limit), renews in \(Int(remainingTime)) seconds, rate: \(rateOfConsumptionPretty)/\(maxRateOfConsumptionPretty), using \(usedRatePercent)% of the allowed request rate." 34 | return report 35 | } 36 | } 37 | 38 | extension GitHubRateLimit: RateLimitType { 39 | 40 | var report: String { 41 | return self.getReport() 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /BuildaGitServer/GitHub/GitHubRepo.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GitHubRepo.swift 3 | // Buildasaur 4 | // 5 | // Created by Honza Dvorsky on 13/12/2014. 6 | // Copyright (c) 2014 Honza Dvorsky. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | class GitHubRepo: GitHubEntity { 12 | 13 | let name: String 14 | let fullName: String 15 | let repoUrlHTTPS: String 16 | let repoUrlSSH: String 17 | let permissionsDict: NSDictionary 18 | 19 | var latestRateLimitInfo: RateLimitType? 20 | 21 | required init(json: NSDictionary) throws { 22 | 23 | self.name = try json.stringForKey("name") 24 | self.fullName = try json.stringForKey("full_name") 25 | self.repoUrlHTTPS = try json.stringForKey("clone_url") 26 | self.repoUrlSSH = try json.stringForKey("ssh_url") 27 | 28 | if let permissions = json.optionalDictionaryForKey("permissions") { 29 | self.permissionsDict = permissions 30 | } else { 31 | self.permissionsDict = NSDictionary() 32 | } 33 | 34 | try super.init(json: json) 35 | } 36 | } 37 | 38 | extension GitHubRepo: RepoType { 39 | 40 | var permissions: RepoPermissions { 41 | 42 | let read = self.permissionsDict["pull"] as? Bool ?? false 43 | let write = self.permissionsDict["push"] as? Bool ?? false 44 | return RepoPermissions(read: read, write: write) 45 | } 46 | 47 | var originUrlSSH: String { 48 | 49 | return self.repoUrlSSH 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /BuildaGitServer/GitHub/GitHubStatus.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Status.swift 3 | // Buildasaur 4 | // 5 | // Created by Honza Dvorsky on 13/12/2014. 6 | // Copyright (c) 2014 Honza Dvorsky. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import BuildaUtils 11 | 12 | class GitHubStatus: GitHubEntity { 13 | 14 | enum GitHubState: String { 15 | case NoState = "" 16 | case Pending = "pending" 17 | case Success = "success" 18 | case Error = "error" 19 | case Failure = "failure" 20 | 21 | static func fromBuildState(buildState: BuildState) -> GitHubState { 22 | switch buildState { 23 | case .NoState: 24 | return .NoState 25 | case .Pending: 26 | return .Pending 27 | case .Success: 28 | return .Success 29 | case .Error: 30 | return .Error 31 | case .Failure: 32 | return .Failure 33 | } 34 | } 35 | 36 | func toBuildState() -> BuildState { 37 | switch self { 38 | case .NoState: 39 | return .NoState 40 | case .Pending: 41 | return .Pending 42 | case .Success: 43 | return .Success 44 | case .Error: 45 | return .Error 46 | case .Failure: 47 | return .Failure 48 | } 49 | } 50 | } 51 | 52 | let githubState: GitHubState 53 | let description: String? 54 | let targetUrl: String? 55 | let context: String? 56 | let created: String? 57 | let creator: GitHubUser? 58 | 59 | required init(json: NSDictionary) throws { 60 | 61 | self.githubState = GitHubState(rawValue: try json.stringForKey("state"))! 62 | self.description = json.optionalStringForKey("description") 63 | self.targetUrl = json.optionalStringForKey("target_url") 64 | self.context = json.optionalStringForKey("context") 65 | self.created = json.optionalStringForKey("created_at") 66 | if let creator = json.optionalDictionaryForKey("creator") { 67 | self.creator = try GitHubUser(json: creator) 68 | } else { 69 | self.creator = nil 70 | } 71 | 72 | try super.init(json: json) 73 | } 74 | 75 | init(state: GitHubState, description: String?, targetUrl: [String: String]?, context: String?) { 76 | 77 | self.githubState = state 78 | self.description = description 79 | self.targetUrl = targetUrl?["https"] 80 | self.context = context 81 | self.creator = nil 82 | self.created = nil 83 | 84 | super.init() 85 | } 86 | 87 | override func dictionarify() -> NSDictionary { 88 | 89 | let dictionary = NSMutableDictionary() 90 | 91 | dictionary["state"] = self.githubState.rawValue 92 | dictionary.optionallyAddValueForKey(self.description as AnyObject, key: "description") 93 | dictionary.optionallyAddValueForKey(self.targetUrl as AnyObject, key: "target_url") 94 | dictionary.optionallyAddValueForKey(self.context as AnyObject, key: "context") 95 | 96 | return dictionary 97 | } 98 | } 99 | 100 | extension GitHubStatus: StatusType { 101 | var state: BuildState { 102 | return self.githubState.toBuildState() 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /BuildaGitServer/GitHub/GitHubUser.swift: -------------------------------------------------------------------------------- 1 | // 2 | // User.swift 3 | // Buildasaur 4 | // 5 | // Created by Honza Dvorsky on 13/12/2014. 6 | // Copyright (c) 2014 Honza Dvorsky. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | class GitHubUser: GitHubEntity { 12 | 13 | let userName: String 14 | let realName: String? 15 | let avatarUrl: String? 16 | 17 | required init(json: NSDictionary) throws { 18 | 19 | self.userName = try json.stringForKey("login") 20 | self.realName = json.optionalStringForKey("name") 21 | self.avatarUrl = try json.stringForKey("avatar_url") 22 | 23 | try super.init(json: json) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /BuildaGitServer/GitServerPublic.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GitSourcePublic.swift 3 | // Buildasaur 4 | // 5 | // Created by Honza Dvorsky on 12/12/2014. 6 | // Copyright (c) 2014 Honza Dvorsky. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import BuildaUtils 11 | import Keys 12 | 13 | public enum GitService: String { 14 | case GitHub = "github" 15 | 16 | public func prettyName() -> String { 17 | switch self { 18 | case .GitHub: return "GitHub" 19 | } 20 | } 21 | 22 | public func logoName() -> String { 23 | switch self { 24 | case .GitHub: return "github" 25 | } 26 | } 27 | 28 | public func hostname() -> String { 29 | switch self { 30 | case .GitHub: return "github.com" 31 | } 32 | } 33 | 34 | public func authorizeUrl() -> String { 35 | switch self { 36 | case .GitHub: return "https://github.com/login/oauth/authorize" 37 | } 38 | } 39 | 40 | public func accessTokenUrl() -> String { 41 | switch self { 42 | case .GitHub: return "https://github.com/login/oauth/access_token" 43 | } 44 | } 45 | 46 | public func serviceKey() -> String { 47 | switch self { 48 | case .GitHub: return BuildasaurKeys().gitHubAPIClientId 49 | } 50 | } 51 | 52 | public func serviceSecret() -> String { 53 | switch self { 54 | case .GitHub: return BuildasaurKeys().gitHubAPIClientSecret 55 | } 56 | } 57 | } 58 | 59 | public class GitServer: HTTPServer { 60 | 61 | let service: GitService 62 | 63 | init(service: GitService, http: HTTP? = nil) { 64 | self.service = service 65 | super.init(http: http) 66 | } 67 | } 68 | 69 | public class GithubServerError: Error { 70 | static func with(_ info: String) -> Error { 71 | return NSError(domain: "GithubServer", code: -1, userInfo: ["info": info]) 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /BuildaGitServer/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | NSHumanReadableCopyright 24 | Copyright © 2014 Honza Dvorsky. All rights reserved. 25 | NSPrincipalClass 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /BuildaGitServerTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /BuildaHeartbeatKit/BuildaHeartbeatKit.h: -------------------------------------------------------------------------------- 1 | // 2 | // BuildaHeartbeatKit.h 3 | // BuildaHeartbeatKit 4 | // 5 | // Created by Honza Dvorsky on 17/09/2015. 6 | // Copyright © 2015 Honza Dvorsky. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for BuildaHeartbeatKit. 12 | FOUNDATION_EXPORT double BuildaHeartbeatKitVersionNumber; 13 | 14 | //! Project version string for BuildaHeartbeatKit. 15 | FOUNDATION_EXPORT const unsigned char BuildaHeartbeatKitVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | -------------------------------------------------------------------------------- /BuildaHeartbeatKit/Heartbeat.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Heartbeat.swift 3 | // Buildasaur 4 | // 5 | // Created by Honza Dvorsky on 17/09/2015. 6 | // Copyright © 2015 Honza Dvorsky. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import ekgclient 11 | import BuildaUtils 12 | 13 | public protocol HeartbeatManagerDelegate: class { 14 | func typesOfRunningSyncers() -> [String: Int] 15 | } 16 | 17 | //READ: https://github.com/czechboy0/Buildasaur/tree/master#heartpulse-heartbeat 18 | @objc public class HeartbeatManager: NSObject { 19 | 20 | public weak var delegate: HeartbeatManagerDelegate? 21 | 22 | private let client: EkgClient 23 | private let creationTime: Double 24 | private var timer: Timer? 25 | private var initialTimer: Timer? 26 | private let interval: Double = 24 * 60 * 60 //send heartbeat once in 24 hours 27 | 28 | public init(server: String) { 29 | let bundle = Bundle.main 30 | let appIdentifier = EkgClientHelper.pullAppIdentifierFromBundle(bundle: bundle) ?? "Unknown app" 31 | let version = EkgClientHelper.pullVersionFromBundle(bundle: bundle) ?? "?" 32 | let buildNumber = EkgClientHelper.pullBuildNumberFromBundle(bundle: bundle) ?? "?" 33 | let appInfo = AppInfo(appIdentifier: appIdentifier, version: version, build: buildNumber) 34 | let host = NSURL(string: server)! 35 | let serverInfo = ServerInfo(host: host) 36 | let userDefaults = UserDefaults.standard 37 | 38 | self.creationTime = NSDate().timeIntervalSince1970 39 | let client = EkgClient(userDefaults: userDefaults, appInfo: appInfo, serverInfo: serverInfo) 40 | self.client = client 41 | } 42 | 43 | deinit { 44 | self.stop() 45 | } 46 | 47 | public func start() { 48 | self.sendLaunchedEvent() 49 | self.startSendingHeartbeat() 50 | } 51 | 52 | public func stop() { 53 | self.stopSendingHeartbeat() 54 | } 55 | 56 | public func willInstallSparkleUpdate() { 57 | self.sendEvent(event: UpdateEvent()) 58 | } 59 | 60 | private func sendEvent(event: Event) { 61 | 62 | Log.info("Sending heartbeat event \(event.jsonify())") 63 | 64 | self.client.sendEvent(event: event) { 65 | if let error = $0 { 66 | Log.error("Failed to send a heartbeat event. Error \(error)") 67 | } 68 | } 69 | } 70 | 71 | private func sendLaunchedEvent() { 72 | self.sendEvent(event: LaunchEvent()) 73 | } 74 | 75 | private func sendHeartbeatEvent() { 76 | let uptime = NSDate().timeIntervalSince1970 - self.creationTime 77 | let typesOfRunningSyncers = self.delegate?.typesOfRunningSyncers() ?? [:] 78 | self.sendEvent(event: HeartbeatEvent(uptime: uptime, typesOfRunningSyncers: typesOfRunningSyncers)) 79 | } 80 | 81 | @objc private func timerFired(timer: Timer?=nil) { 82 | self.sendHeartbeatEvent() 83 | 84 | if let initialTimer = self.initialTimer, initialTimer.isValid { 85 | initialTimer.invalidate() 86 | self.initialTimer = nil 87 | } 88 | } 89 | 90 | private func startSendingHeartbeat() { 91 | 92 | //send once in 10 seconds to give builda a chance to init and start 93 | self.initialTimer?.invalidate() 94 | self.initialTimer = Timer.scheduledTimer( 95 | timeInterval: 20, 96 | target: self, 97 | selector: #selector(timerFired(timer:)), 98 | userInfo: nil, 99 | repeats: false) 100 | 101 | self.timer?.invalidate() 102 | self.timer = Timer.scheduledTimer( 103 | timeInterval: self.interval, 104 | target: self, 105 | selector: #selector(timerFired(timer:)), 106 | userInfo: nil, 107 | repeats: true) 108 | } 109 | 110 | private func stopSendingHeartbeat() { 111 | self.timer?.invalidate() 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /BuildaHeartbeatKit/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | NSHumanReadableCopyright 24 | Copyright © 2015 Honza Dvorsky. All rights reserved. 25 | NSPrincipalClass 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /BuildaKit/Availability.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Availability.swift 3 | // Buildasaur 4 | // 5 | // Created by Honza Dvorsky on 10/6/15. 6 | // Copyright © 2015 Honza Dvorsky. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import BuildaUtils 11 | import XcodeServerSDK 12 | 13 | public class AvailabilityChecker { 14 | public static func xcodeServerAvailability(config: XcodeServerConfig, onUpdate: @escaping (_ state: AvailabilityCheckState) -> Void) { 15 | onUpdate(.checking) 16 | 17 | NetworkUtils.checkAvailabilityOfXcodeServerWithCurrentSettings(config: config) { (success, error) -> Void in 18 | OperationQueue.main.addOperation { 19 | if success { 20 | onUpdate(.succeeded) 21 | } else { 22 | onUpdate(.failed(error)) 23 | } 24 | } 25 | } 26 | } 27 | 28 | public static func projectAvailability(config: ProjectConfig, onUpdate: @escaping (_ state: AvailabilityCheckState) -> Void) { 29 | onUpdate(.checking) 30 | 31 | var project: Project! 32 | do { 33 | project = try Project(config: config) 34 | } catch { 35 | onUpdate(.failed(error)) 36 | return 37 | } 38 | 39 | NetworkUtils.checkAvailabilityOfServiceWithProject(project: project) { (success, error) -> Void in 40 | OperationQueue.main.addOperation { 41 | if success { 42 | onUpdate(.succeeded) 43 | } else { 44 | onUpdate(.failed(error)) 45 | } 46 | } 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /BuildaKit/BlueprintFileParser.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BlueprintFileParser.swift 3 | // Buildasaur 4 | // 5 | // Created by Honza Dvorsky on 10/21/15. 6 | // Copyright © 2015 Honza Dvorsky. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import BuildaUtils 11 | 12 | class BlueprintFileParser: SourceControlFileParser { 13 | 14 | func supportedFileExtensions() -> [String] { 15 | return ["xcscmblueprint"] 16 | } 17 | 18 | func parseFileAtUrl(url: URL) throws -> WorkspaceMetadata { 19 | 20 | //JSON -> NSDictionary 21 | let data = try Data(contentsOf: url, options: NSData.ReadingOptions()) 22 | let jsonObject = try JSONSerialization.jsonObject(with: data, options: []) 23 | guard let dictionary = jsonObject as? NSDictionary else { throw XcodeDeviceParserError.with("Failed to parse \(url)") } 24 | 25 | //parse our required keys 26 | let projectName = dictionary.optionalStringForKey("DVTSourceControlWorkspaceBlueprintNameKey") 27 | let projectPath = dictionary.optionalStringForKey("DVTSourceControlWorkspaceBlueprintRelativePathToProjectKey") 28 | let projectWCCIdentifier = dictionary.optionalStringForKey("DVTSourceControlWorkspaceBlueprintPrimaryRemoteRepositoryKey") 29 | 30 | var primaryRemoteRepositoryDictionary: NSDictionary? 31 | if let wccId = projectWCCIdentifier { 32 | if let wcConfigs = dictionary["DVTSourceControlWorkspaceBlueprintRemoteRepositoriesKey"] as? [NSDictionary] { 33 | primaryRemoteRepositoryDictionary = wcConfigs.first(where: { 34 | if let loopWccId = $0.optionalStringForKey("DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey") { 35 | return loopWccId == wccId 36 | } 37 | return false 38 | }) 39 | } 40 | } 41 | 42 | let projectURLString = primaryRemoteRepositoryDictionary?.optionalStringForKey("DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey") 43 | 44 | var projectWCCName: String? 45 | if 46 | let copyPaths = dictionary["DVTSourceControlWorkspaceBlueprintWorkingCopyPathsKey"] as? [String: String], 47 | let primaryRemoteRepoId = projectWCCIdentifier 48 | { 49 | projectWCCName = copyPaths[primaryRemoteRepoId] 50 | } 51 | 52 | return try WorkspaceMetadata(projectName: projectName, projectPath: projectPath, projectWCCIdentifier: projectWCCIdentifier, projectWCCName: projectWCCName, projectURLString: projectURLString) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /BuildaKit/BuildaKit.h: -------------------------------------------------------------------------------- 1 | // 2 | // BuildaKit.h 3 | // BuildaKit 4 | // 5 | // Created by Honza Dvorsky on 18/07/2015. 6 | // Copyright © 2015 Honza Dvorsky. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for BuildaKit. 12 | FOUNDATION_EXPORT double BuildaKitVersionNumber; 13 | 14 | //! Project version string for BuildaKit. 15 | FOUNDATION_EXPORT const unsigned char BuildaKitVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | -------------------------------------------------------------------------------- /BuildaKit/CheckoutFileParser.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CheckoutFileParser.swift 3 | // Buildasaur 4 | // 5 | // Created by Honza Dvorsky on 10/21/15. 6 | // Copyright © 2015 Honza Dvorsky. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import BuildaUtils 11 | 12 | class CheckoutFileParser: SourceControlFileParser { 13 | 14 | func supportedFileExtensions() -> [String] { 15 | return ["xccheckout"] 16 | } 17 | 18 | func parseFileAtUrl(url: URL) throws -> WorkspaceMetadata { 19 | 20 | //plist -> NSDictionary 21 | guard let dictionary = NSDictionary(contentsOf: url) else { throw XcodeDeviceParserError.with("Failed to parse \(url)") } 22 | 23 | //parse our required keys 24 | let projectName = dictionary.optionalStringForKey("IDESourceControlProjectName") 25 | let projectPath = dictionary.optionalStringForKey("IDESourceControlProjectPath") 26 | let projectWCCIdentifier = dictionary.optionalStringForKey("IDESourceControlProjectWCCIdentifier") 27 | let projectWCCName = { () -> String? in 28 | if let wccId = projectWCCIdentifier { 29 | if let wcConfigs = dictionary["IDESourceControlProjectWCConfigurations"] as? [NSDictionary] { 30 | if let foundConfig = wcConfigs.first(where: { 31 | if let loopWccId = $0.optionalStringForKey("IDESourceControlWCCIdentifierKey") { 32 | return loopWccId == wccId 33 | } 34 | return false 35 | }) { 36 | //so much effort for this little key... 37 | return foundConfig.optionalStringForKey("IDESourceControlWCCName") 38 | } 39 | } 40 | } 41 | return nil 42 | }() 43 | let projectURLString = { dictionary.optionalStringForKey("IDESourceControlProjectURL") }() 44 | 45 | return try WorkspaceMetadata(projectName: projectName, projectPath: projectPath, projectWCCIdentifier: projectWCCIdentifier, projectWCCName: projectWCCName, projectURLString: projectURLString) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /BuildaKit/ConfigTriplet.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ConfigTriplet.swift 3 | // Buildasaur 4 | // 5 | // Created by Honza Dvorsky on 10/10/15. 6 | // Copyright © 2015 Honza Dvorsky. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import XcodeServerSDK 11 | 12 | //TODO: remove invalid configs on startup? 13 | 14 | public struct ConfigTriplet { 15 | public var syncer: SyncerConfig 16 | public var server: XcodeServerConfig 17 | public var project: ProjectConfig 18 | public var buildTemplate: BuildTemplate 19 | public var triggers: [TriggerConfig] 20 | 21 | init(syncer: SyncerConfig, server: XcodeServerConfig, project: ProjectConfig, buildTemplate: BuildTemplate, triggers: [TriggerConfig]) { 22 | self.syncer = syncer 23 | self.server = server 24 | self.project = project 25 | self.buildTemplate = buildTemplate 26 | self.syncer.preferredTemplateRef = buildTemplate.id 27 | self.triggers = triggers 28 | } 29 | 30 | public func toEditable() -> EditableConfigTriplet { 31 | return EditableConfigTriplet(syncer: self.syncer, server: self.server, project: self.project, buildTemplate: self.buildTemplate, triggers: self.triggers) 32 | } 33 | } 34 | 35 | public struct EditableConfigTriplet { 36 | public var syncer: SyncerConfig 37 | public var server: XcodeServerConfig? 38 | public var project: ProjectConfig? 39 | public var buildTemplate: BuildTemplate? 40 | public var triggers: [TriggerConfig]? 41 | 42 | public func toFinal() -> ConfigTriplet { 43 | var syncer = self.syncer 44 | syncer.preferredTemplateRef = self.buildTemplate!.id 45 | return ConfigTriplet(syncer: syncer, server: self.server!, project: self.project!, buildTemplate: self.buildTemplate!, triggers: self.triggers!) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /BuildaKit/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | NSHumanReadableCopyright 24 | Copyright © 2015 Honza Dvorsky. All rights reserved. 25 | NSPrincipalClass 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /BuildaKit/Logging.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Logging.swift 3 | // Buildasaur 4 | // 5 | // Created by Honza Dvorsky on 19/05/15. 6 | // Copyright (c) 2015 Honza Dvorsky. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import BuildaUtils 11 | 12 | public class Logging { 13 | 14 | public class func setup(persistence: Persistence, alsoIntoFile: Bool) { 15 | Log.addLoggers([ConsoleLogger()]) 16 | self.setupFileLogger(persistence: persistence, enable: alsoIntoFile) 17 | 18 | let version = Bundle.main.infoDictionary!["CFBundleShortVersionString"] as! String 19 | let ascii = 20 | " ____ _ _ _\n" + 21 | "| _ \\ (_) | | |\n" + 22 | "| |_) |_ _ _| | __| | __ _ ___ __ _ _ _ _ __\n" + 23 | "| _ <| | | | | |/ _` |/ _` / __|/ _` | | | | '__|\n" + 24 | "| |_) | |_| | | | (_| | (_| \\__ \\ (_| | |_| | |\n" + 25 | "|____/ \\__,_|_|_|\\__,_|\\__,_|___/\\__,_|\\__,_|_|\n" 26 | 27 | Log.untouched("*\n*\n*\n\(ascii)\nBuildasaur \(version) launched at \(NSDate()).\n*\n*\n*\n") 28 | } 29 | 30 | public class func setupFileLogger(persistence: Persistence, enable: Bool) { 31 | let path = persistence 32 | .fileURLWithName(name: "Logs", intention: .Writing, isDirectory: true) 33 | .appendingPathComponent("Builda.log", isDirectory: false) 34 | 35 | if enable && !Log.loggers.contains(where: { $0.id().hasSuffix(String(describing: FileLogger.self)) }) { 36 | let fileLogger = FileLogger(fileURL: path) 37 | fileLogger.fileSizeCap = 1024 * 1024 * 10 // 10MB 38 | Log.addLoggers([fileLogger]) 39 | } else if let fileLogger = Log.loggers.first(where: { $0.id().hasSuffix(String(describing: FileLogger.self)) }), 40 | !enable { 41 | Log.removeLogger(fileLogger) 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /BuildaKit/LoginItem.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LoginItem.swift 3 | // Buildasaur 4 | // 5 | // Created by Honza Dvorsky on 19/05/15. 6 | // Copyright (c) 2015 Honza Dvorsky. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import BuildaUtils 11 | 12 | //manages adding/removing Buildasaur as a login item 13 | 14 | public class LoginItem { 15 | 16 | public init() { } 17 | 18 | public var isLaunchItem: Bool { 19 | get { 20 | return self.hasPlistInstalled() 21 | } 22 | set { 23 | if newValue { 24 | do { 25 | try self.addLaunchItemPlist() 26 | } catch { 27 | Log.error("Error while adding login item: \(error)") 28 | } 29 | } else { 30 | self.removeLaunchItemPlist() 31 | } 32 | } 33 | } 34 | 35 | private func hasPlistInstalled() -> Bool { 36 | return FileManager.default.fileExists(atPath: self.launchItemPlistURL().path) 37 | } 38 | 39 | private func launchItemPlistURL() -> URL { 40 | let path = ("~/Library/LaunchAgents/com.honzadvorsky.Buildasaur.plist" as NSString).expandingTildeInPath 41 | let url = URL(fileURLWithPath: path, isDirectory: false) 42 | return url 43 | } 44 | 45 | private func currentBinaryPath() -> String { 46 | 47 | let processInfo = ProcessInfo.processInfo 48 | let launchPath = processInfo.arguments.first! 49 | return launchPath 50 | } 51 | 52 | private func launchItemPlistWithLaunchPath(launchPath: String) throws -> String { 53 | 54 | let plistStringUrl = Bundle.main.url(forResource: "launch_item", withExtension: "plist")! 55 | let plistString = try String(contentsOf: plistStringUrl) 56 | 57 | //replace placeholder with launch path 58 | let patchedPlistString = plistString.replacingOccurrences(of: "LAUNCH_PATH_PLACEHOLDER", with: launchPath) 59 | return patchedPlistString 60 | } 61 | 62 | public func removeLaunchItemPlist() { 63 | _ = try? FileManager.default.removeItem(at: self.launchItemPlistURL()) 64 | } 65 | 66 | public func addLaunchItemPlist() throws { 67 | let launchPath = self.currentBinaryPath() 68 | let contents = try self.launchItemPlistWithLaunchPath(launchPath: launchPath) 69 | let url = self.launchItemPlistURL() 70 | try contents.write(to: url as URL, atomically: true, encoding: String.Encoding.utf8) 71 | } 72 | 73 | } 74 | -------------------------------------------------------------------------------- /BuildaKit/Project.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Project.swift 3 | // Buildasaur 4 | // 5 | // Created by Honza Dvorsky on 14/02/2015. 6 | // Copyright (c) 2015 Honza Dvorsky. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import BuildaUtils 11 | import XcodeServerSDK 12 | 13 | open class Project { 14 | public var url: URL { 15 | return URL(fileURLWithPath: self.config.url, isDirectory: true) 16 | } 17 | 18 | public var config: ProjectConfig { 19 | didSet { 20 | try? self.refreshMetadata() 21 | } 22 | } 23 | 24 | public var urlString: String { return self.url.absoluteString } 25 | public var privateSSHKey: String? { return self.getContentsOfKeyAtPath(path: self.config.privateSSHKeyPath) } 26 | public var publicSSHKey: String? { return self.getContentsOfKeyAtPath(path: self.config.publicSSHKeyPath) } 27 | 28 | public var availabilityState: AvailabilityCheckState = .unchecked 29 | 30 | private(set) public var workspaceMetadata: WorkspaceMetadata? 31 | 32 | public init(config: ProjectConfig) throws { 33 | self.config = config 34 | try? self.refreshMetadata() 35 | } 36 | 37 | private init(original: Project, forkOriginURL: String) throws { 38 | self.config = original.config 39 | self.workspaceMetadata = try original.workspaceMetadata?.duplicateWithForkURL(forkUrlString: forkOriginURL) 40 | } 41 | 42 | public func duplicateForForkAtOriginURL(forkURL: String) throws -> Project { 43 | return try Project(original: self, forkOriginURL: forkURL) 44 | } 45 | 46 | public class func attemptToParseFromUrl(url: URL) throws -> WorkspaceMetadata { 47 | return try Project.loadWorkspaceMetadata(url: url) 48 | } 49 | 50 | private func refreshMetadata() throws { 51 | self.workspaceMetadata = try Project.attemptToParseFromUrl(url: self.url) 52 | } 53 | 54 | public func schemes() -> [XcodeScheme] { 55 | return XcodeProjectParser.sharedSchemesFromProjectOrWorkspaceUrl(url: self.url) 56 | } 57 | 58 | private class func loadWorkspaceMetadata(url: URL) throws -> WorkspaceMetadata { 59 | return try XcodeProjectParser.parseRepoMetadataFromProjectOrWorkspaceURL(url: url) 60 | } 61 | 62 | public func serviceRepoName() -> String? { 63 | guard let meta = self.workspaceMetadata else { return nil } 64 | 65 | let projectUrl = meta.projectURL 66 | let service = meta.service 67 | 68 | let originalStringUrl = projectUrl.absoluteString 69 | let stringUrl = originalStringUrl!.lowercased() 70 | 71 | /* 72 | both https and ssh repos on github have a form of: 73 | {https://|git@}SERVICE_URL{:|/}organization/repo.git 74 | here I need the organization/repo bit, which I'll do by finding "SERVICE_URL" and shifting right by one 75 | and scan up until ".git" 76 | */ 77 | 78 | let serviceUrl = service.hostname().lowercased() 79 | let dotGitRange = stringUrl.range(of: ".git", options: NSString.CompareOptions.backwards, range: nil, locale: nil) ?? stringUrl.endIndex.. String? { 91 | let url = URL(fileURLWithPath: path) 92 | do { 93 | let key = try NSString(contentsOf: url, encoding: String.Encoding.ascii.rawValue) 94 | return key as String 95 | } catch { 96 | Log.error("Couldn't load key at url \(url) with error \(error)") 97 | } 98 | return nil 99 | } 100 | 101 | } 102 | -------------------------------------------------------------------------------- /BuildaKit/ProjectConfig.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ProjectConfig.swift 3 | // Buildasaur 4 | // 5 | // Created by Honza Dvorsky on 10/3/15. 6 | // Copyright © 2015 Honza Dvorsky. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import BuildaUtils 11 | import BuildaGitServer 12 | 13 | public struct ProjectConfig { 14 | public let id: RefType 15 | public var url: String 16 | public var privateSSHKeyPath: String 17 | public var publicSSHKeyPath: String 18 | 19 | public var sshPassphrase: String? //loaded from the keychain 20 | public var serverAuthentication: ProjectAuthenticator? //loaded from the keychain 21 | 22 | //creates a new default ProjectConfig 23 | public init() { 24 | self.id = Ref.new() 25 | self.url = "" 26 | self.serverAuthentication = nil 27 | self.privateSSHKeyPath = "" 28 | self.publicSSHKeyPath = "" 29 | self.sshPassphrase = nil 30 | } 31 | 32 | public func validate() throws { 33 | //TODO: throw of required keys are not valid 34 | } 35 | } 36 | 37 | private struct Keys { 38 | 39 | static let URL = "url" 40 | static let PrivateSSHKeyPath = "ssh_private_key_url" 41 | static let PublicSSHKeyPath = "ssh_public_key_url" 42 | static let Id = "id" 43 | } 44 | 45 | extension ProjectConfig: JSONSerializable { 46 | 47 | public func jsonify() -> [String: Any] { 48 | var json: [String: Any] = [:] 49 | json[Keys.URL] = self.url 50 | json[Keys.PrivateSSHKeyPath] = self.privateSSHKeyPath 51 | json[Keys.PublicSSHKeyPath] = self.publicSSHKeyPath 52 | json[Keys.Id] = self.id 53 | return json 54 | } 55 | 56 | public init(json: [String: Any]) throws { 57 | self.url = json[Keys.URL] as! String 58 | self.privateSSHKeyPath = json[Keys.PrivateSSHKeyPath] as! String 59 | self.publicSSHKeyPath = json[Keys.PublicSSHKeyPath] as! String 60 | self.id = json[Keys.Id] as! String 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /BuildaKit/SSHKeyVerification.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SSHKeyVerification.swift 3 | // Buildasaur 4 | // 5 | // Created by Honza Dvorsky on 13/05/15. 6 | // Copyright (c) 2015 Honza Dvorsky. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import BuildaUtils 11 | 12 | public class SSHKeyVerification { 13 | 14 | private class func findXcodeDeveloperFolder() -> String { 15 | 16 | //first find xcode's developer folder, in case user has a renamed xcode 17 | let found = Script.run("xcode-select", arguments: ["-p"]) 18 | 19 | if found.terminationStatus == 0 { 20 | let path = found.standardOutput.stripTrailingNewline() 21 | return path 22 | } else { 23 | //if that fails, try the standard path 24 | return "/Applications/Xcode.app/Contents/Developer" 25 | } 26 | } 27 | 28 | public class func verifyBlueprint(blueprint: NSDictionary) -> Script.ScriptResponse { 29 | 30 | do { 31 | //convert dictionary into string 32 | let data = try JSONSerialization.data(withJSONObject: blueprint, options: []) 33 | 34 | let scriptString = String(data: data, encoding: String.Encoding.utf8)! 35 | 36 | let xcodePath = self.findXcodeDeveloperFolder() 37 | let xcsbridgePath = "\(xcodePath)/usr/bin/xcsbridge" 38 | let xcsbridgeArgs = "source-control blueprint-preflight --path - --format json" 39 | let script = "echo '\(scriptString)' | \(xcsbridgePath) \(xcsbridgeArgs)" 40 | let response = Script.runTemporaryScript(script) 41 | 42 | //if return value != 0, something went wrong unexpectedly, just pass up 43 | if response.terminationStatus != 0 { 44 | return response 45 | } 46 | 47 | //parse the response as json 48 | let responseString = response.standardOutput 49 | if 50 | let data = responseString.data(using: String.Encoding.utf8), 51 | let obj = try JSONSerialization.jsonObject(with: data, options: .allowFragments) as? NSDictionary 52 | { 53 | 54 | //valid output is an empty dictionary 55 | // swiftlint:disable empty_count 56 | if obj.count == 0 { 57 | // swiftlint:enable empty_count 58 | //yay 59 | return (0, "Blueprint is valid", "") 60 | } 61 | 62 | //else, we know a key "repository errors" having a sensible error, try to parse it 63 | if 64 | let repositoryErrors = obj["repositoryErrors"] as? NSArray, 65 | let errorDict = repositoryErrors.firstObject as? NSDictionary, 66 | let errorObject = errorDict["error"] as? NSDictionary, 67 | let errorMessage = errorObject["message"] as? String 68 | { 69 | return (1, "", errorMessage) 70 | } else { 71 | return (1, "", obj.description) 72 | } 73 | 74 | } 75 | return response 76 | 77 | } catch { 78 | return (1, "", "\(error)") 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /BuildaKit/SecurePersistence.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SecurePersistence.swift 3 | // Buildasaur 4 | // 5 | // Created by Honza Dvorsky on 1/22/16. 6 | // Copyright © 2016 Honza Dvorsky. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import KeychainAccess 11 | import XcodeServerSDK 12 | import SwiftSafe 13 | 14 | final class SecurePersistence { 15 | 16 | #if TESTING 17 | typealias Keychain = NSMutableDictionary 18 | #endif 19 | 20 | #if RELEASE 21 | static let Prefix = "com.honzadvorsky.buildasaur" 22 | #else 23 | static let Prefix = "com.honzadvorsky.buildasaur.debug" 24 | #endif 25 | 26 | private let keychain: Keychain 27 | private let safe: Safe 28 | 29 | private init(keychain: Keychain, safe: Safe = EREW()) { 30 | self.keychain = keychain 31 | self.safe = safe 32 | } 33 | 34 | static func xcodeServerPasswordKeychain() -> SecurePersistence { 35 | return self.keychain(service: "\(Prefix).xcs.password") 36 | } 37 | 38 | static func sourceServerTokenKeychain() -> SecurePersistence { 39 | return self.keychain(service: "\(Prefix).source_server.oauth_tokens") 40 | } 41 | 42 | static func sourceServerPassphraseKeychain() -> SecurePersistence { 43 | return self.keychain(service: "\(Prefix).source_server.passphrase") 44 | } 45 | 46 | static private func keychain(service: String) -> SecurePersistence { 47 | #if TESTING 48 | let keychain = NSMutableDictionary() 49 | #else 50 | let keychain = Keychain(service: service) 51 | #endif 52 | return self.init(keychain: keychain) 53 | } 54 | 55 | func read(key: String) -> String? { 56 | var val: String? 57 | self.safe.read { 58 | #if TESTING 59 | val = self.keychain[key] as? String 60 | #else 61 | val = self.keychain[key] 62 | #endif 63 | } 64 | return val 65 | } 66 | 67 | func readAll() -> [(String, String)] { 68 | var all: [(String, String)] = [] 69 | self.safe.read { 70 | #if TESTING 71 | let keychain = self.keychain 72 | all = keychain.allKeys.map { ($0 as! String, keychain[$0 as! String] as! String) } 73 | #else 74 | let keychain = self.keychain 75 | all = keychain.allKeys().map { ($0, keychain[$0]!) } 76 | #endif 77 | } 78 | return all 79 | } 80 | 81 | func writeIfNeeded(key: String, value: String?) { 82 | self.safe.write { 83 | self.updateIfNeeded(key: key, value: value) 84 | } 85 | } 86 | 87 | private func updateIfNeeded(key: String, value: String?) { 88 | #if TESTING 89 | let existing = self.keychain[key] as? String 90 | #else 91 | let existing = self.keychain[key] 92 | #endif 93 | if existing != value { 94 | self.keychain[key] = value 95 | } 96 | } 97 | } 98 | 99 | public protocol KeychainSaveable { 100 | func keychainKey() -> String 101 | } 102 | 103 | extension XcodeServerConfig: KeychainSaveable { 104 | public func keychainKey() -> String { 105 | return "\(self.host):\(self.user ?? "")" 106 | } 107 | } 108 | 109 | extension ProjectConfig: KeychainSaveable { 110 | public func keychainKey() -> String { 111 | return self.id 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /BuildaKit/SourceControlFileParser.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SourceControlFileParser.swift 3 | // Buildasaur 4 | // 5 | // Created by Honza Dvorsky on 29/09/2015. 6 | // Copyright © 2015 Honza Dvorsky. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | protocol SourceControlFileParser { 12 | 13 | func supportedFileExtensions() -> [String] 14 | func parseFileAtUrl(url: URL) throws -> WorkspaceMetadata 15 | } 16 | -------------------------------------------------------------------------------- /BuildaKit/StandardSyncer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StandardSyncer.swift 3 | // Buildasaur 4 | // 5 | // Created by Honza Dvorsky on 15/02/2015. 6 | // Copyright (c) 2015 Honza Dvorsky. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import BuildaGitServer 11 | import XcodeServerSDK 12 | 13 | public class StandardSyncer: Syncer { 14 | public var sourceServer: SourceServerType { 15 | didSet { 16 | self.onRequireUIUpdate?() 17 | } 18 | } 19 | 20 | public var sourceNotifier: Notifier 21 | public var slackNotifier: SlackNotifier? 22 | 23 | public var xcodeServer: XcodeServer { 24 | didSet { 25 | if oldValue != self.xcodeServer { 26 | self.onRequireUIUpdate?() 27 | } 28 | } 29 | } 30 | public var project: Project { 31 | didSet { 32 | self.onRequireUIUpdate?() 33 | } 34 | } 35 | public var buildTemplate: BuildTemplate { 36 | didSet { 37 | self.onRequireUIUpdate?() 38 | } 39 | } 40 | public var triggers: [Trigger] { 41 | didSet { 42 | self.onRequireUIUpdate?() 43 | } 44 | } 45 | 46 | public override var active: Bool { 47 | didSet { 48 | if oldValue != self.active { 49 | self.onRequireUIUpdate?() 50 | } 51 | } 52 | } 53 | 54 | public var config: SyncerConfig { 55 | didSet { 56 | self.syncInterval = self.config.syncInterval 57 | self.onRequireUIUpdate?() 58 | } 59 | } 60 | 61 | public override var state: SyncerEventType { 62 | didSet { 63 | if oldValue != self.state { 64 | self.onRequireUIUpdate?() 65 | } 66 | } 67 | } 68 | 69 | public var onRequireUIUpdate: (() -> Void)? 70 | public var onRequireLog: (() -> Void)? 71 | 72 | public var configTriplet: ConfigTriplet { 73 | return ConfigTriplet(syncer: self.config, server: self.xcodeServer.config, project: self.project.config, buildTemplate: self.buildTemplate, triggers: self.triggers.map { $0.config }) 74 | } 75 | 76 | public init(integrationServer: XcodeServer, sourceServer: SourceServerType & Notifier, project: Project, buildTemplate: BuildTemplate, triggers: [Trigger], config: SyncerConfig) { 77 | self.config = config 78 | 79 | self.sourceServer = sourceServer 80 | self.sourceNotifier = sourceServer 81 | self.xcodeServer = integrationServer 82 | self.project = project 83 | self.buildTemplate = buildTemplate 84 | self.triggers = triggers 85 | 86 | if let slackWebhook = self.config.slackWebhook, 87 | let url = URL(string: slackWebhook) { 88 | self.slackNotifier = SlackNotifier(webhookURL: url) 89 | } 90 | 91 | super.init(syncInterval: config.syncInterval) 92 | } 93 | 94 | deinit { 95 | self.active = false 96 | } 97 | 98 | public override func sync(completion: @escaping () -> Void) { 99 | if let repoName = self.repoName() { 100 | self.syncRepoWithName(repoName: repoName) { [weak self] watchingBranches in 101 | if let watchingBranches = watchingBranches { 102 | self?.config.watchingBranches = watchingBranches 103 | } 104 | completion() 105 | } 106 | } else { 107 | self.notifyErrorString(errorString: "Nil repo name", context: "Syncing") 108 | completion() 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /BuildaKit/StorageUtils.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Utils.swift 3 | // Buildasaur 4 | // 5 | // Created by Honza Dvorsky on 24/01/2015. 6 | // Copyright (c) 2015 Honza Dvorsky. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Cocoa 11 | import BuildaUtils 12 | import XcodeServerSDK 13 | 14 | public class StorageUtils { 15 | 16 | public class func openWorkspaceOrProject() -> URL? { 17 | 18 | let openPanel = NSOpenPanel() 19 | openPanel.canChooseDirectories = false 20 | openPanel.canChooseFiles = true 21 | openPanel.allowsMultipleSelection = false 22 | openPanel.allowedFileTypes = ["xcworkspace", "xcodeproj"] 23 | openPanel.title = "Select your Project or Workspace" 24 | 25 | let clicked = openPanel.runModal() 26 | 27 | switch clicked { 28 | case .OK: 29 | let url = openPanel.url 30 | let urlOrEmpty = url ?? NSURL() as URL 31 | Log.info("Project: \(urlOrEmpty)") 32 | return url 33 | default: 34 | //do nothing 35 | Log.verbose("Dismissed open dialog") 36 | } 37 | return nil 38 | } 39 | 40 | public class func openSSHKey(publicOrPrivate: String) -> URL? { 41 | 42 | let openPanel = NSOpenPanel() 43 | openPanel.canChooseDirectories = false 44 | openPanel.canChooseFiles = true 45 | openPanel.allowsMultipleSelection = false 46 | openPanel.allowedFileTypes = ["", "pub"] 47 | openPanel.title = "Select your \(publicOrPrivate) SSH key" 48 | openPanel.showsHiddenFiles = true 49 | 50 | let clicked = openPanel.runModal() 51 | 52 | switch clicked { 53 | case .OK: 54 | let url = openPanel.url 55 | Log.info("Key: \(url?.description ?? "nil")") 56 | return url 57 | default: 58 | //do nothing 59 | Log.verbose("Dismissed open dialog") 60 | } 61 | return nil 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /BuildaKit/SyncPair.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SyncPair.swift 3 | // Buildasaur 4 | // 5 | // Created by Honza Dvorsky on 16/05/2015. 6 | // Copyright (c) 2015 Honza Dvorsky. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import BuildaUtils 11 | import XcodeServerSDK 12 | 13 | /* 14 | * this class describes the basic sync element: e.g. a PR + Bot, a branch + Bot, a branch + no bot, a bot + no PR 15 | * each sync pair has its own behaviors (a branch + no bot creates a bot, a bot + no PR deletes the bot, 16 | * a PR + Bot figures out what to do next, ...) 17 | * this is simpler than trying to catch all cases in one giant syncer class (at least I think) 18 | */ 19 | public class SyncPair { 20 | 21 | var syncer: StandardSyncer! 22 | 23 | init() { 24 | // 25 | } 26 | 27 | typealias Completion = (_ error: Error?) -> Void 28 | 29 | /** 30 | * Call to perform sync. 31 | */ 32 | final func start(completion: @escaping Completion) { 33 | 34 | let start = Date() 35 | // Log.verbose("SyncPair \(self.syncPairName()) started sync") 36 | 37 | self.sync { (error) -> Void in 38 | 39 | let duration = -1 * start.timeIntervalSinceNow.clipTo(3) 40 | Log.verbose("SyncPair \(self.syncPairName()) finished sync after \(duration) seconds.") 41 | completion(error) 42 | } 43 | } 44 | 45 | /** 46 | * To be overriden by subclasses. 47 | */ 48 | func sync(completion: @escaping Completion) { 49 | assertionFailure("Must be overriden by subclasses") 50 | } 51 | 52 | /** 53 | * To be overriden by subclasses. 54 | */ 55 | func syncPairName() -> String { 56 | assertionFailure("Must be overriden by subclasses") 57 | return "" 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /BuildaKit/SyncPairBranchResolver.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SyncPairBranchResolver.swift 3 | // Buildasaur 4 | // 5 | // Created by Honza Dvorsky on 19/05/15. 6 | // Copyright (c) 2015 Honza Dvorsky. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import XcodeServerSDK 11 | import BuildaGitServer 12 | import BuildaUtils 13 | 14 | public class SyncPairBranchResolver: SyncPairResolver { 15 | 16 | } 17 | -------------------------------------------------------------------------------- /BuildaKit/SyncPairPRResolver.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SyncPairPRResolver.swift 3 | // Buildasaur 4 | // 5 | // Created by Honza Dvorsky on 19/05/15. 6 | // Copyright (c) 2015 Honza Dvorsky. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import XcodeServerSDK 11 | import BuildaGitServer 12 | import BuildaUtils 13 | 14 | public class SyncPairPRResolver: SyncPairResolver { 15 | 16 | } 17 | -------------------------------------------------------------------------------- /BuildaKit/SyncPair_Branch_Bot.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SyncPair_Branch_Bot.swift 3 | // Buildasaur 4 | // 5 | // Created by Honza Dvorsky on 19/05/15. 6 | // Copyright (c) 2015 Honza Dvorsky. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import XcodeServerSDK 11 | import BuildaGitServer 12 | import BuildaUtils 13 | 14 | public class SyncPair_Branch_Bot: SyncPair { 15 | 16 | let branch: BranchType 17 | let bot: Bot 18 | let resolver: SyncPairBranchResolver 19 | 20 | public init(branch: BranchType, bot: Bot, resolver: SyncPairBranchResolver) { 21 | self.branch = branch 22 | self.bot = bot 23 | self.resolver = resolver 24 | super.init() 25 | } 26 | 27 | override func sync(completion: @escaping Completion) { 28 | 29 | //sync the branch with the bot 30 | self.syncBranchWithBot(completion: completion) 31 | } 32 | 33 | override func syncPairName() -> String { 34 | return "Branch (\(self.branch.name)) + Bot (\(self.bot.name))" 35 | } 36 | 37 | // MARK: Internal 38 | 39 | private func syncBranchWithBot(completion: @escaping Completion) { 40 | 41 | let bot = self.bot 42 | let branch = self.branch.name 43 | let headCommit = self.branch.commitSHA 44 | let issue: IssueType? = nil //TODO: only pull/create if we're failing 45 | 46 | self.syncer.xcodeServer.getHostname { (hostname, error) -> Void in 47 | 48 | if let error = error { 49 | completion(error) 50 | return 51 | } 52 | 53 | self.getIntegrations(bot: bot, completion: { (integrations, error) -> Void in 54 | 55 | if let error = error { 56 | completion(error) 57 | return 58 | } 59 | 60 | let actions = self.resolver.resolveActionsForCommitAndIssueWithBotIntegrations( 61 | commit: headCommit, 62 | branch: branch, 63 | issue: issue, 64 | bot: bot, 65 | hostname: hostname!, 66 | buildStatusCreator: self.syncer, 67 | integrations: integrations) 68 | 69 | //in case of branches, we also (optionally) want to add functionality for creating an issue if the branch starts failing and updating with comments the same way we do with PRs. 70 | //also, when the build is finally successful on the branch, the issue will be automatically closed. 71 | //TODO: add this functionality here and add it as another action available from a sync pair 72 | 73 | self.performActions(actions: actions, completion: completion) 74 | }) 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /BuildaKit/SyncPair_Branch_NoBot.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SyncPair_Branch_NoBot.swift 3 | // Buildasaur 4 | // 5 | // Created by Honza Dvorsky on 20/05/15. 6 | // Copyright (c) 2015 Honza Dvorsky. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import XcodeServerSDK 11 | import BuildaGitServer 12 | 13 | class SyncPair_Branch_NoBot: SyncPair { 14 | 15 | let branch: BranchType 16 | let repo: RepoType 17 | 18 | init(branch: BranchType, repo: RepoType) { 19 | self.branch = branch 20 | self.repo = repo 21 | super.init() 22 | } 23 | 24 | override func sync(completion: @escaping Completion) { 25 | 26 | //create a bot for this branch 27 | let syncer = self.syncer 28 | let branch = self.branch 29 | let repo = self.repo 30 | 31 | SyncPair_Branch_NoBot.createBotForBranch(syncer: syncer!, branch: branch, repo: repo, completion: completion) 32 | } 33 | 34 | override func syncPairName() -> String { 35 | return "Branch (\(self.branch.name)) + No Bot" 36 | } 37 | 38 | // MARK: Internal 39 | 40 | private class func createBotForBranch(syncer: StandardSyncer, branch: BranchType, repo: RepoType, completion: @escaping Completion) { 41 | 42 | syncer.createBotFromBranch(branch: branch, repo: repo, completion: { () -> Void in 43 | completion(nil) 44 | }) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /BuildaKit/SyncPair_Deletable_Bot.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SyncPair_Deletable_Bot.swift 3 | // Buildasaur 4 | // 5 | // Created by Honza Dvorsky on 16/05/2015. 6 | // Copyright (c) 2015 Honza Dvorsky. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import BuildaGitServer 11 | import XcodeServerSDK 12 | 13 | class SyncPair_Deletable_Bot: SyncPair { 14 | let bot: Bot 15 | 16 | init(bot: Bot) { 17 | self.bot = bot 18 | super.init() 19 | } 20 | 21 | override func sync(completion: @escaping Completion) { 22 | //delete the bot 23 | let syncer = self.syncer 24 | let bot = self.bot 25 | 26 | SyncPair_Deletable_Bot.deleteBot(syncer: syncer!, bot: bot, completion: completion) 27 | } 28 | 29 | override func syncPairName() -> String { 30 | return "Deletable Bot (\(self.bot.name))" 31 | } 32 | 33 | private class func deleteBot(syncer: StandardSyncer, bot: Bot, completion: @escaping Completion) { 34 | syncer.deleteBot(bot: bot, completion: { () -> Void in 35 | completion(nil) 36 | }) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /BuildaKit/SyncPair_PR_NoBot.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SyncPair_PR_NoBot.swift 3 | // Buildasaur 4 | // 5 | // Created by Honza Dvorsky on 16/05/2015. 6 | // Copyright (c) 2015 Honza Dvorsky. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import BuildaGitServer 11 | 12 | class SyncPair_PR_NoBot: SyncPair { 13 | 14 | let pr: PullRequestType 15 | 16 | init(pr: PullRequestType) { 17 | self.pr = pr 18 | super.init() 19 | } 20 | 21 | override func sync(completion: @escaping Completion) { 22 | 23 | //create a bot for this PR 24 | let syncer = self.syncer 25 | let pr = self.pr 26 | 27 | SyncPair_PR_NoBot.createBotForPR(syncer: syncer!, pr: pr, completion: completion) 28 | } 29 | 30 | override func syncPairName() -> String { 31 | return "PR (\(self.pr.headName)) + No Bot" 32 | } 33 | 34 | // MARK: Internal 35 | 36 | private class func createBotForPR(syncer: StandardSyncer, pr: PullRequestType, completion: @escaping Completion) { 37 | 38 | syncer.createBotFromPR(pr: pr, completion: { () -> Void in 39 | completion(nil) 40 | }) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /BuildaKit/SyncerBotNaming.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BotNaming.swift 3 | // Buildasaur 4 | // 5 | // Created by Honza Dvorsky on 16/05/2015. 6 | // Copyright (c) 2015 Honza Dvorsky. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import XcodeServerSDK 11 | import BuildaGitServer 12 | 13 | class BotNaming { 14 | 15 | class func isBuildaBot(bot: Bot) -> Bool { 16 | return bot.name.hasPrefix(self.prefixForBuildaBot()) 17 | } 18 | 19 | class func isBuildaBotBelongingToRepoWithName(bot: Bot, repoName: String) -> Bool { 20 | return bot.name.hasPrefix(self.prefixForBuildaBotInRepoWithName(repoName: repoName)) 21 | } 22 | 23 | class func nameForBotWithBranch(branch: BranchType, repoName: String) -> String { 24 | return "\(self.prefixForBuildaBotInRepoWithName(repoName: repoName)) |-> \(branch.name)" 25 | } 26 | 27 | class func nameForBotWithPR(pr: PullRequestType, repoName: String) -> String { 28 | return "\(self.prefixForBuildaBotInRepoWithName(repoName: repoName)) PR #\(pr.number)" 29 | } 30 | 31 | class func prefixForBuildaBotInRepoWithName(repoName: String) -> String { 32 | return "\(self.prefixForBuildaBot()) [\(repoName)]" 33 | } 34 | 35 | class func prefixForBuildaBot() -> String { 36 | return "BuildaBot" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /BuildaKit/SyncerNotifierUtils.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SyncerNotifierUtils.swift 3 | // Buildasaur 4 | // 5 | // Created by Honza Dvorsky on 16/05/2015. 6 | // Copyright (c) 2015 Honza Dvorsky. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import BuildaGitServer 11 | import BuildaUtils 12 | 13 | extension StandardSyncer: BuildStatusCreator { 14 | 15 | public func createStatusFromState(state: BuildState, description: String?, targetUrl: [String: String]?) -> StatusType { 16 | 17 | return self._sourceServer.createStatusFromState(state: state, description: description, targetUrl: targetUrl) 18 | } 19 | } 20 | 21 | extension StandardSyncer { 22 | func updateCommitStatusIfNecessary( 23 | newStatus: StatusAndComment, 24 | commit: String, 25 | branch: String, 26 | issue: IssueType?, 27 | issues: String? = nil, 28 | completion: @escaping SyncPair.Completion) { 29 | let repoName = self.repoName()! 30 | self._sourceServer.getStatusOfCommit(commit: commit, repo: repoName, completion: { (status, error) -> Void in 31 | 32 | if error != nil { 33 | let e = XcodeDeviceParserError.with("Commit \(commit) failed to return status") 34 | completion(e) 35 | return 36 | } 37 | 38 | if newStatus.status.state != status?.state { 39 | 40 | //TODO: add logic for handling the creation of a new Issue for branch tracking 41 | //and the deletion of it when build succeeds etc. 42 | 43 | // Update commit status 44 | self._sourceServer.postStatusOfCommit(commit: commit, status: newStatus.status, repo: repoName) { (_, error) -> Void in 45 | 46 | if let error = error as NSError? { 47 | let e = XcodeDeviceParserError.with("Failed to post a status on commit \(commit) of repo \(repoName) \(error.userInfo["info"]!)") 48 | completion(e) 49 | return 50 | } 51 | 52 | completion(nil) 53 | } 54 | 55 | if let comment = newStatus.comment { 56 | let notifierNotification = NotifierNotification(comment: comment, 57 | issueNumber: issue?.number, 58 | repo: repoName, 59 | branch: branch, 60 | status: newStatus.status, 61 | integrationResult: newStatus.integration?.result?.rawValue, 62 | linksToIntegration: newStatus.links, 63 | issues: issues) 64 | 65 | self._notifiers?.forEach { 66 | $0.postCommentOnIssue(notification: notifierNotification, completion: { (comment, error) -> Void in 67 | if let error = error { 68 | let issueNumber: String 69 | if let number = issue?.number { 70 | issueNumber = "\(number)" 71 | } else { 72 | issueNumber = "-" 73 | } 74 | let e = XcodeDeviceParserError.with("Failed to post a comment \"\(String(describing: comment))\" on Issue \(issueNumber) of repo \(repoName) \(error)") 75 | completion(e) 76 | } else { 77 | completion(nil) 78 | } 79 | }) 80 | } 81 | } 82 | } else { 83 | completion(nil) 84 | } 85 | }) 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /BuildaKit/TriggerConfig.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TriggerConfig.swift 3 | // BuildaKit 4 | // 5 | // Created by Sylvain Fay-Chatelard on 20/11/2017. 6 | // Copyright © 2017 Honza Dvorsky. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import BuildaUtils 11 | import XcodeServerSDK 12 | 13 | //HACK: move to XcodeServerSDK 14 | extension TriggerConfig: JSONReadable, JSONWritable { 15 | public init(json: [String: Any]) throws { 16 | let phase = Phase(rawValue: json["phase"] as! Int)! 17 | self.phase = phase 18 | if let conditionsJSON = json["conditions"] as? NSDictionary, phase == .postbuild { 19 | //also parse conditions 20 | self.conditions = try TriggerConditions(json: conditionsJSON) 21 | } else { 22 | self.conditions = nil 23 | } 24 | 25 | let kind = Kind(rawValue: json["type"] as! Int)! 26 | self.kind = kind 27 | if let configurationJSON = json["emailConfiguration"] as? NSDictionary, kind == .emailNotification { 28 | //also parse email config 29 | self.emailConfiguration = try EmailConfiguration(json: configurationJSON) 30 | } else { 31 | self.emailConfiguration = nil 32 | } 33 | 34 | self.name = json["name"] as! String 35 | self.scriptBody = json["scriptBody"] as! String 36 | 37 | self.id = json["id"] as? RefType ?? Ref.new() 38 | } 39 | 40 | public func jsonify() -> [String: Any] { 41 | var dict: [String: Any] = [:] 42 | dict["id"] = self.id 43 | dict["phase"] = self.phase.rawValue 44 | dict["type"] = self.kind.rawValue 45 | dict["scriptBody"] = self.scriptBody 46 | dict["name"] = self.name 47 | if let conditions = self.conditions { 48 | dict["conditions"] = conditions.dictionarify() 49 | } 50 | if let emailConfiguration = self.emailConfiguration { 51 | dict["emailConfiguration"] = emailConfiguration.dictionarify() 52 | } 53 | return dict 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /BuildaKit/XcodeDeviceParser.swift: -------------------------------------------------------------------------------- 1 | // 2 | // XcodeDeviceParser.swift 3 | // Buildasaur 4 | // 5 | // Created by Honza Dvorsky on 30/06/2015. 6 | // Copyright © 2015 Honza Dvorsky. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import XcodeServerSDK 11 | import BuildaUtils 12 | 13 | public class XcodeDeviceParserError: Error { 14 | static func with(_ info: String) -> Error { 15 | return NSError(domain: "GithubServer", code: -1, userInfo: ["info": info]) 16 | } 17 | } 18 | 19 | public class XcodeDeviceParser { 20 | 21 | public enum DeviceType: String { 22 | case iPhoneOS = "iphoneos" 23 | case macOSX = "macosx" 24 | case watchOS = "watchos" 25 | case tvOS = "appletvos" 26 | 27 | public func toPlatformType() -> DevicePlatform.PlatformType { 28 | switch self { 29 | case .iPhoneOS: 30 | return .iOS 31 | case .macOSX: 32 | return .OSX 33 | case .watchOS: 34 | return .watchOS 35 | case .tvOS: 36 | return .tvOS 37 | } 38 | } 39 | } 40 | 41 | public class func parseDeviceTypeFromProjectUrlAndScheme(projectUrl: URL, scheme: XcodeScheme) throws -> DeviceType { 42 | let typeString = try self.parseTargetTypeFromSchemeAndProjectAtUrl(scheme: scheme, projectFolderUrl: projectUrl) 43 | guard let deviceType = DeviceType(rawValue: typeString) else { 44 | throw XcodeDeviceParserError.with("Unrecognized type: \(typeString)") 45 | } 46 | return deviceType 47 | } 48 | 49 | private class func parseTargetTypeFromSchemeAndProjectAtUrl(scheme: XcodeScheme, projectFolderUrl: URL) throws -> String { 50 | let ownerArgs = try { () throws -> String in 51 | 52 | let ownerUrl = scheme.ownerProjectOrWorkspace.path! 53 | switch (scheme.ownerProjectOrWorkspace.lastPathComponent! as NSString).pathExtension { 54 | case "xcworkspace": 55 | return "-workspace \"\(ownerUrl)\"" 56 | case "xcodeproj": 57 | return "-project \"\(ownerUrl)\"" 58 | default: 59 | throw XcodeDeviceParserError.with("Unrecognized project/workspace path \(ownerUrl)") 60 | } 61 | }() 62 | 63 | let folder = projectFolderUrl.deletingLastPathComponent().path 64 | let schemeName = scheme.name 65 | 66 | let script = "cd \"\(folder)\"; xcodebuild \(ownerArgs) -scheme \"\(schemeName)\" -showBuildSettings 2>/dev/null | egrep '^\\s*PLATFORM_NAME' | cut -d = -f 2 | uniq | xargs echo" 67 | let res = Script.runTemporaryScript(script) 68 | if res.terminationStatus == 0 { 69 | let deviceType = res.standardOutput.stripTrailingNewline() 70 | return deviceType 71 | } 72 | throw XcodeDeviceParserError.with("Termination status: \(res.terminationStatus), output: \(res.standardOutput), error: \(res.standardError)") 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /BuildaKit/XcodeProject.swift: -------------------------------------------------------------------------------- 1 | // 2 | // XcodeProject.swift 3 | // Buildasaur 4 | // 5 | // Created by Honza Dvorsky on 15/02/2015. 6 | // Copyright (c) 2015 Honza Dvorsky. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import XcodeServerSDK 11 | 12 | extension Project { 13 | public func createSourceControlBlueprint(branch: String) -> SourceControlBlueprint { 14 | let workspaceMetadata = self.workspaceMetadata! 15 | 16 | let projectWCCIdentifier = workspaceMetadata.projectWCCIdentifier 17 | let wccName = workspaceMetadata.projectWCCName 18 | let projectName = workspaceMetadata.projectName 19 | let projectURL = workspaceMetadata.projectURL.absoluteString 20 | let projectPath = workspaceMetadata.projectPath 21 | let publicSSHKey = self.publicSSHKey 22 | let privateSSHKey = self.privateSSHKey 23 | let sshPassphrase = self.config.sshPassphrase 24 | 25 | let blueprint = SourceControlBlueprint(branch: branch, projectWCCIdentifier: projectWCCIdentifier, wCCName: wccName, projectName: projectName, projectURL: projectURL!, projectPath: projectPath, publicSSHKey: publicSSHKey, privateSSHKey: privateSSHKey, sshPassphrase: sshPassphrase) 26 | return blueprint 27 | } 28 | 29 | public func createSourceControlBlueprintForCredentialVerification() -> SourceControlBlueprint { 30 | let projectURL = self.workspaceMetadata!.projectURL.absoluteString 31 | let publicSSHKey = self.publicSSHKey 32 | let privateSSHKey = self.privateSSHKey 33 | let sshPassphrase = self.config.sshPassphrase 34 | 35 | let blueprint = SourceControlBlueprint(projectURL: projectURL!, publicSSHKey: publicSSHKey, privateSSHKey: privateSSHKey, sshPassphrase: sshPassphrase) 36 | return blueprint 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /BuildaKit/XcodeProjectXMLParser.swift: -------------------------------------------------------------------------------- 1 | // 2 | // XcodeProjectXMLParser.swift 3 | // Buildasaur 4 | // 5 | // Created by Honza Dvorsky on 02/10/2015. 6 | // Copyright © 2015 Honza Dvorsky. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Ji 11 | 12 | class XcodeProjectXMLParser { 13 | 14 | enum WorkspaceParsingError: Error { 15 | case ParsingFailed 16 | case FailedToFindWorkspaceNode 17 | case NoProjectsFound 18 | case NoLocationInProjectFound 19 | } 20 | 21 | static func parseProjectsInsideOfWorkspace(url: URL) throws -> [URL] { 22 | 23 | let contentsUrl = url.appendingPathComponent("contents.xcworkspacedata") 24 | 25 | guard let jiDoc = Ji(contentsOfURL: contentsUrl, isXML: true) else { throw WorkspaceParsingError.ParsingFailed } 26 | guard 27 | let workspaceNode = jiDoc.rootNode, 28 | let workspaceTag = workspaceNode.tag, workspaceTag == "Workspace" else { throw WorkspaceParsingError.FailedToFindWorkspaceNode } 29 | 30 | let projects = workspaceNode.childrenWithName("FileRef") 31 | guard !projects.isEmpty else { throw WorkspaceParsingError.NoProjectsFound } 32 | 33 | let locations = try projects.map { projectNode throws -> String in 34 | guard let location = projectNode["location"] else { throw WorkspaceParsingError.NoLocationInProjectFound } 35 | return location 36 | } 37 | 38 | let parsedRelativePaths = locations.map { $0.split(separator: ":").last! } 39 | let absolutePaths = parsedRelativePaths.map { return url.appendingPathComponent("..").appendingPathComponent($0) } 40 | return absolutePaths 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /BuildaKit/XcodeScheme.swift: -------------------------------------------------------------------------------- 1 | // 2 | // XcodeScheme.swift 3 | // Buildasaur 4 | // 5 | // Created by Honza Dvorsky on 02/10/2015. 6 | // Copyright © 2015 Honza Dvorsky. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension NSURL { 12 | 13 | public var fileNameNoExtension: String? { 14 | return ((self.lastPathComponent ?? "") as NSString).deletingPathExtension 15 | } 16 | } 17 | 18 | public struct XcodeScheme { 19 | 20 | public var name: String { 21 | return self.path.fileNameNoExtension! 22 | } 23 | 24 | public let path: NSURL 25 | public let ownerProjectOrWorkspace: NSURL 26 | } 27 | -------------------------------------------------------------------------------- /BuildaKit/XcodeServerSyncerUtils.swift: -------------------------------------------------------------------------------- 1 | // 2 | // XcodeServerSyncerUtils.swift 3 | // Buildasaur 4 | // 5 | // Created by Honza Dvorsky on 15/03/2015. 6 | // Copyright (c) 2015 Honza Dvorsky. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import XcodeServerSDK 11 | import BuildaGitServer 12 | import BuildaUtils 13 | 14 | public class XcodeServerSyncerUtils { 15 | 16 | public class func createBotFromBuildTemplate(botName: String, syncer: StandardSyncer, template: BuildTemplate, project: Project, branch: String, scheduleOverride: BotSchedule?, xcodeServer: XcodeServer, completion: @escaping (_ bot: Bot?, _ error: Error?) -> Void) { 17 | 18 | //pull info from template 19 | let schemeName = template.scheme 20 | 21 | //optionally override the schedule, if nil, takes it from the template 22 | let schedule = scheduleOverride ?? template.schedule 23 | let cleaningPolicy = template.cleaningPolicy 24 | let triggers = syncer.triggers 25 | let analyze = template.shouldAnalyze 26 | let test = template.shouldTest 27 | let archive = template.shouldArchive 28 | 29 | //TODO: create a device spec from testing devices and filter type (and scheme target type?) 30 | let testingDeviceIds = template.testingDeviceIds 31 | let filterType = template.deviceFilter 32 | let platformType = template.platformType ?? .iOS //default to iOS for no reason 33 | let architectureType = DeviceFilter.ArchitectureType.architectureFromPlatformType(platformType) 34 | 35 | let devicePlatform = DevicePlatform(type: platformType) 36 | let deviceFilter = DeviceFilter(platform: devicePlatform, filterType: filterType, architectureType: architectureType) 37 | 38 | let deviceSpecification = DeviceSpecification(filters: [deviceFilter], deviceIdentifiers: testingDeviceIds) 39 | 40 | let blueprint = project.createSourceControlBlueprint(branch: branch) 41 | 42 | //create bot config 43 | let botConfiguration = BotConfiguration( 44 | builtFromClean: cleaningPolicy, 45 | analyze: analyze, 46 | test: test, 47 | archive: archive, 48 | schemeName: schemeName, 49 | schedule: schedule, 50 | triggers: triggers, 51 | deviceSpecification: deviceSpecification, 52 | sourceControlBlueprint: blueprint) 53 | 54 | //create the bot finally 55 | let newBot = Bot(name: botName, configuration: botConfiguration) 56 | 57 | xcodeServer.createBot(newBot, completion: { (response) -> Void in 58 | 59 | var outBot: Bot? 60 | var outError: Error? 61 | switch response { 62 | case .success(let bot): 63 | //we good 64 | Log.info("Successfully created bot \(bot.name)") 65 | outBot = bot 66 | case .error(let error): 67 | outError = error 68 | default: 69 | outError = XcodeServerError.with("Failed to return bot after creation even after error was nil!") 70 | } 71 | 72 | //print success/failure etc 73 | if let error = outError { 74 | Log.error("Failed to create bot with name \(botName) and json \(newBot.dictionarify()), error \(error)") 75 | } 76 | 77 | OperationQueue.main.addOperation { 78 | completion(outBot, outError) 79 | } 80 | }) 81 | } 82 | 83 | } 84 | -------------------------------------------------------------------------------- /BuildaKitTests/ExtensionTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ExtensionTests.swift 3 | // Buildasaur 4 | // 5 | // Created by Anton Domashnev on 25/06/16. 6 | // Copyright © 2016 Honza Dvorsky. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | class ExtensionTests: XCTestCase { 12 | 13 | func testMergeShouldMergeTwoDictionaries() { 14 | var dictionary1: [String: String] = ["A": "B", "A1": "B1", "A2": "B2" ] 15 | let dictionary2: [String: String] = ["A2": "B2", "A3": "B3", "A4": "B4" ] 16 | let expectedMergedDictionary: [String: String] = ["A": "B", "A1": "B1", "A2": "B2", "A3": "B3", "A4": "B4" ] 17 | 18 | dictionary1.merge(dictionary2) { (k1, _) in k1 } 19 | 20 | XCTAssertEqual(dictionary1, expectedMergedDictionary) 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /BuildaKitTests/GeneralTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GeneralTests.swift 3 | // Buildasaur 4 | // 5 | // Created by Honza Dvorsky on 10/4/15. 6 | // Copyright © 2015 Honza Dvorsky. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import BuildaKit 11 | 12 | class GeneralTests: XCTestCase { 13 | 14 | func testXcodeWorkspaceParsing() { 15 | 16 | let projectUrl = URL(fileURLWithPath: MockProject().config.url, isDirectory: true) 17 | let projects = try? XcodeProjectXMLParser.parseProjectsInsideOfWorkspace(url: projectUrl) 18 | XCTAssert(projects != nil) 19 | XCTAssert(projects?.count ?? 0 > 0) 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /BuildaKitTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /BuildaKitTests/Migration/Buildasaur-format-0-example1/BuildTemplates/B31C6530-BB84-4A93-B08C-54074AAB5F37.json: -------------------------------------------------------------------------------- 1 | { 2 | "id" : "B31C6530-BB84-4A93-B08C-54074AAB5F37", 3 | "name" : "BuildaTest PR", 4 | "project_name" : "Buildasaur-Tester", 5 | "triggers" : [ 6 | { 7 | "phase" : 1, 8 | "scriptBody" : "echo \"hello\"\n", 9 | "type" : 1, 10 | "name" : "Prebuild Script" 11 | }, 12 | { 13 | "phase" : 2, 14 | "scriptBody" : "", 15 | "emailConfiguration" : { 16 | "includeCommitMessages" : true, 17 | "additionalRecipients" : [ 18 | "h@d.com", 19 | "yo@ma.lo" 20 | ], 21 | "emailCommitters" : false, 22 | "includeIssueDetails" : true 23 | }, 24 | "type" : 2, 25 | "name" : "Postbuild Email", 26 | "conditions" : { 27 | "status" : 2, 28 | "onWarnings" : true, 29 | "onBuildErrors" : true, 30 | "onInternalErrors" : true, 31 | "onAnalyzerWarnings" : true, 32 | "onFailingTests" : true, 33 | "onSuccess" : true 34 | } 35 | } 36 | ], 37 | "should_analyze" : true, 38 | "platform_type" : "com.apple.platform.iphoneos", 39 | "scheme" : "Buildasaur-Tester", 40 | "device_filter" : 0, 41 | "cleaning_policy" : 0, 42 | "testing_devices" : [ 43 | 44 | ], 45 | "should_archive" : false, 46 | "should_test" : true, 47 | "schedule" : { 48 | "weeklyScheduleDay" : 0, 49 | "periodicScheduleInterval" : 0, 50 | "hourOfIntegration" : 0, 51 | "minutesAfterHourToIntegrate" : 0, 52 | "scheduleType" : 3 53 | } 54 | } -------------------------------------------------------------------------------- /BuildaKitTests/Migration/Buildasaur-format-0-example1/Config.json: -------------------------------------------------------------------------------- 1 | { 2 | 3 | } -------------------------------------------------------------------------------- /BuildaKitTests/Migration/Buildasaur-format-0-example1/Projects.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "preferred_template_id" : "B31C6530-BB84-4A93-B08C-54074AAB5F37", 4 | "ssh_public_key_url" : "file:\/\/\/Users\/honzadvorsky\/.ssh\/id_rsa.pub", 5 | "url" : "file:\/\/\/Users\/honzadvorsky\/Documents\/Buildasaur-Tester\/Buildasaur-Tester.xcodeproj", 6 | "github_token" : "example_token-123456786543456765432", 7 | "ssh_private_key_url" : "file:\/\/\/Users\/honzadvorsky\/.ssh\/id_rsa" 8 | } 9 | ] -------------------------------------------------------------------------------- /BuildaKitTests/Migration/Buildasaur-format-0-example1/ServerConfigs.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "password" : "SuperSecretPa55word", 4 | "host" : "https:\/\/127.0.0.1", 5 | "user" : "testadmin" 6 | } 7 | ] -------------------------------------------------------------------------------- /BuildaKitTests/Migration/Buildasaur-format-0-example1/Syncers.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "project_path" : "file:\/\/\/Users\/honzadvorsky\/Documents\/Buildasaur-Tester\/Buildasaur-Tester.xcodeproj", 4 | "wait_for_lttm" : false, 5 | "watched_branches" : [ 6 | "master" 7 | ], 8 | "post_status_comments" : false, 9 | "server_host" : "https:\/\/127.0.0.1", 10 | "sync_interval" : 19 11 | } 12 | ] -------------------------------------------------------------------------------- /BuildaKitTests/Migration/Buildasaur-format-1-example1/BuildTemplates/B31C6530-BB84-4A93-B08C-54074AAB5F37.json: -------------------------------------------------------------------------------- 1 | { 2 | "id" : "B31C6530-BB84-4A93-B08C-54074AAB5F37", 3 | "name" : "BuildaTest PR", 4 | "project_name" : "Buildasaur-Tester", 5 | "triggers" : [ 6 | { 7 | "phase" : 1, 8 | "scriptBody" : "echo \"hello\"\n", 9 | "type" : 1, 10 | "name" : "Prebuild Script" 11 | }, 12 | { 13 | "phase" : 2, 14 | "scriptBody" : "", 15 | "emailConfiguration" : { 16 | "includeCommitMessages" : true, 17 | "additionalRecipients" : [ 18 | "h@d.com", 19 | "yo@ma.lo" 20 | ], 21 | "emailCommitters" : false, 22 | "includeIssueDetails" : true 23 | }, 24 | "type" : 2, 25 | "name" : "Postbuild Email", 26 | "conditions" : { 27 | "status" : 2, 28 | "onWarnings" : true, 29 | "onBuildErrors" : true, 30 | "onInternalErrors" : true, 31 | "onAnalyzerWarnings" : true, 32 | "onFailingTests" : true, 33 | "onSuccess" : true 34 | } 35 | } 36 | ], 37 | "should_analyze" : true, 38 | "platform_type" : "com.apple.platform.iphoneos", 39 | "scheme" : "Buildasaur-Tester", 40 | "device_filter" : 0, 41 | "cleaning_policy" : 0, 42 | "testing_devices" : [ 43 | 44 | ], 45 | "should_archive" : false, 46 | "should_test" : true, 47 | "schedule" : { 48 | "weeklyScheduleDay" : 0, 49 | "periodicScheduleInterval" : 0, 50 | "hourOfIntegration" : 0, 51 | "minutesAfterHourToIntegrate" : 0, 52 | "scheduleType" : 3 53 | } 54 | } -------------------------------------------------------------------------------- /BuildaKitTests/Migration/Buildasaur-format-1-example1/Config.json: -------------------------------------------------------------------------------- 1 | { 2 | "persistence_version" : 1 3 | } -------------------------------------------------------------------------------- /BuildaKitTests/Migration/Buildasaur-format-1-example1/Projects.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "preferred_template_id" : "B31C6530-BB84-4A93-B08C-54074AAB5F37", 4 | "ssh_public_key_url" : "file:\/\/\/Users\/honzadvorsky\/.ssh\/id_rsa.pub", 5 | "url" : "file:\/\/\/Users\/honzadvorsky\/Documents\/Buildasaur-Tester\/Buildasaur-Tester.xcodeproj", 6 | "github_token" : "example_token-123456786543456765432", 7 | "ssh_private_key_url" : "file:\/\/\/Users\/honzadvorsky\/.ssh\/id_rsa" 8 | } 9 | ] -------------------------------------------------------------------------------- /BuildaKitTests/Migration/Buildasaur-format-1-example1/ServerConfigs.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "password" : "SuperSecretPa55word", 4 | "host" : "https:\/\/127.0.0.1", 5 | "user" : "testadmin" 6 | } 7 | ] -------------------------------------------------------------------------------- /BuildaKitTests/Migration/Buildasaur-format-1-example1/Syncers.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "project_path" : "file:\/\/\/Users\/honzadvorsky\/Documents\/Buildasaur-Tester\/Buildasaur-Tester.xcodeproj", 4 | "wait_for_lttm" : false, 5 | "watched_branches" : [ 6 | "master" 7 | ], 8 | "post_status_comments" : false, 9 | "server_host" : "https:\/\/127.0.0.1", 10 | "sync_interval" : 19 11 | } 12 | ] -------------------------------------------------------------------------------- /BuildaKitTests/Migration/Buildasaur-format-2-example1/BuildTemplates/B31C6530-BB84-4A93-B08C-54074AAB5F37.json: -------------------------------------------------------------------------------- 1 | { 2 | "id" : "B31C6530-BB84-4A93-B08C-54074AAB5F37", 3 | "project_name" : "Buildasaur-Tester", 4 | "should_test" : true, 5 | "triggers" : [ 6 | "E8F5285A-A262-4630-AF7B-236772B75760", 7 | "4E66D0D5-D5CC-417E-A40E-73B513CE4E10" 8 | ], 9 | "should_analyze" : true, 10 | "platform_type" : "com.apple.platform.iphoneos", 11 | "scheme" : "Buildasaur-Tester", 12 | "device_filter" : 0, 13 | "cleaning_policy" : 0, 14 | "testing_devices" : [ 15 | 16 | ], 17 | "should_archive" : false, 18 | "name" : "BuildaTest PR", 19 | "schedule" : { 20 | "weeklyScheduleDay" : 0, 21 | "periodicScheduleInterval" : 0, 22 | "hourOfIntegration" : 0, 23 | "minutesAfterHourToIntegrate" : 0, 24 | "scheduleType" : 3 25 | } 26 | } -------------------------------------------------------------------------------- /BuildaKitTests/Migration/Buildasaur-format-2-example1/Config.json: -------------------------------------------------------------------------------- 1 | { 2 | "persistence_version" : 2 3 | } -------------------------------------------------------------------------------- /BuildaKitTests/Migration/Buildasaur-format-2-example1/Projects.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "ssh_public_key_url" : "\/Users\/honzadvorsky\/.ssh\/id_rsa.pub", 4 | "id" : "4E8E7708-01FB-448A-B929-A54887CC5857", 5 | "url" : "\/Users\/honzadvorsky\/Documents\/Buildasaur-Tester\/Buildasaur-Tester.xcodeproj", 6 | "github_token" : "example_token-123456786543456765432", 7 | "ssh_private_key_url" : "\/Users\/honzadvorsky\/.ssh\/id_rsa" 8 | } 9 | ] -------------------------------------------------------------------------------- /BuildaKitTests/Migration/Buildasaur-format-2-example1/ServerConfigs.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "password" : "SuperSecretPa55word", 4 | "id" : "D143B09C-CB1B-4831-8BA1-E2F8AB039B56", 5 | "host" : "https:\/\/127.0.0.1", 6 | "user" : "testadmin" 7 | } 8 | ] -------------------------------------------------------------------------------- /BuildaKitTests/Migration/Buildasaur-format-2-example1/Syncers.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "preferred_template_ref" : "B31C6530-BB84-4A93-B08C-54074AAB5F37", 4 | "id" : "564C267D-FF06-4008-9EF6-66B3AC1A3BDE", 5 | "sync_interval" : 19, 6 | "server_ref" : "D143B09C-CB1B-4831-8BA1-E2F8AB039B56", 7 | "wait_for_lttm" : false, 8 | "watched_branches" : [ 9 | "master" 10 | ], 11 | "project_ref" : "4E8E7708-01FB-448A-B929-A54887CC5857", 12 | "post_status_comments" : false 13 | } 14 | ] -------------------------------------------------------------------------------- /BuildaKitTests/Migration/Buildasaur-format-2-example1/Triggers/4E66D0D5-D5CC-417E-A40E-73B513CE4E10.json: -------------------------------------------------------------------------------- 1 | { 2 | "phase" : 2, 3 | "id" : "4E66D0D5-D5CC-417E-A40E-73B513CE4E10", 4 | "scriptBody" : "", 5 | "conditions" : { 6 | "status" : 2, 7 | "onWarnings" : true, 8 | "onBuildErrors" : true, 9 | "onInternalErrors" : true, 10 | "onAnalyzerWarnings" : true, 11 | "onFailingTests" : true, 12 | "onSuccess" : true 13 | }, 14 | "type" : 2, 15 | "name" : "Postbuild Email", 16 | "emailConfiguration" : { 17 | "includeCommitMessages" : true, 18 | "additionalRecipients" : [ 19 | "h@d.com", 20 | "yo@ma.lo" 21 | ], 22 | "emailCommitters" : false, 23 | "includeIssueDetails" : true 24 | } 25 | } -------------------------------------------------------------------------------- /BuildaKitTests/Migration/Buildasaur-format-2-example1/Triggers/E8F5285A-A262-4630-AF7B-236772B75760.json: -------------------------------------------------------------------------------- 1 | { 2 | "phase" : 1, 3 | "scriptBody" : "echo \"hello\"\n", 4 | "id" : "E8F5285A-A262-4630-AF7B-236772B75760", 5 | "type" : 1, 6 | "name" : "Prebuild Script" 7 | } -------------------------------------------------------------------------------- /BuildaKitTests/Migration/Buildasaur-format-2-example2/BuildTemplates/9B53CC35-57DA-4DA0-9B85-05FCF109512A.json: -------------------------------------------------------------------------------- 1 | { 2 | "id" : "9B53CC35-57DA-4DA0-9B85-05FCF109512A", 3 | "project_name" : "Buildasaur", 4 | "schedule" : { 5 | "weeklyScheduleDay" : 0, 6 | "periodicScheduleInterval" : 0, 7 | "hourOfIntegration" : 0, 8 | "minutesAfterHourToIntegrate" : 0, 9 | "scheduleType" : 3 10 | }, 11 | "triggers" : [ 12 | "E8F5285A-A262-4630-AF7B-236772B75760", 13 | "4E66D0D5-D5CC-417E-A40E-73B513CE4E10" 14 | ], 15 | "should_analyze" : true, 16 | "platform_type" : "com.apple.platform.macosx", 17 | "scheme" : "Buildasaur", 18 | "device_filter" : 0, 19 | "cleaning_policy" : 0, 20 | "testing_devices" : [ 21 | 22 | ], 23 | "should_archive" : false, 24 | "name" : "Buildasaur PR", 25 | "should_test" : true 26 | } -------------------------------------------------------------------------------- /BuildaKitTests/Migration/Buildasaur-format-2-example2/BuildTemplates/B31C6530-BB84-4A93-B08C-54074AAB5F37.json: -------------------------------------------------------------------------------- 1 | { 2 | "id" : "B31C6530-BB84-4A93-B08C-54074AAB5F37", 3 | "project_name" : "Buildasaur-Tester", 4 | "schedule" : { 5 | "weeklyScheduleDay" : 0, 6 | "periodicScheduleInterval" : 0, 7 | "hourOfIntegration" : 0, 8 | "minutesAfterHourToIntegrate" : 0, 9 | "scheduleType" : 3 10 | }, 11 | "triggers" : [ 12 | "E8F5285A-A262-4630-AF7B-236772B75760", 13 | "4E66D0D5-D5CC-417E-A40E-73B513CE4E10" 14 | ], 15 | "should_analyze" : true, 16 | "platform_type" : "com.apple.platform.iphoneos", 17 | "scheme" : "Buildasaur-Tester", 18 | "device_filter" : 0, 19 | "cleaning_policy" : 0, 20 | "testing_devices" : [ 21 | 22 | ], 23 | "should_archive" : false, 24 | "name" : "BuildaTest PR", 25 | "should_test" : true 26 | } -------------------------------------------------------------------------------- /BuildaKitTests/Migration/Buildasaur-format-2-example2/Config.json: -------------------------------------------------------------------------------- 1 | { 2 | "persistence_version" : 2 3 | } -------------------------------------------------------------------------------- /BuildaKitTests/Migration/Buildasaur-format-2-example2/Projects.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "ssh_public_key_url" : "\/Users\/honzadvorsky\/.ssh\/id_rsa.pub", 4 | "id" : "4E8E7708-01FB-448A-B929-A54887CC5857", 5 | "url" : "\/Users\/honzadvorsky\/Documents\/Buildasaur-Tester\/Buildasaur-Tester.xcodeproj", 6 | "github_token" : "example_token-123456786543456765432", 7 | "ssh_private_key_url" : "\/Users\/honzadvorsky\/.ssh\/id_rsa", 8 | "ssh_passphrase": "my_passphrase_much_secret" 9 | }, 10 | { 11 | "ssh_public_key_url" : "\/Users\/honzadvorsky\/.ssh\/id_rsa.pub", 12 | "id" : "D316F062-7FEC-497A-B20E-6776AA413009", 13 | "url" : "\/Users\/honzadvorsky\/Documents\/Buildasaur\/Buildasaur.xcodeproj", 14 | "github_token" : "example_token-44446786543456765432", 15 | "ssh_private_key_url" : "\/Users\/honzadvorsky\/.ssh\/id_rsa", 16 | "ssh_passphrase": "my_passphrase_much_secret_2" 17 | } 18 | ] -------------------------------------------------------------------------------- /BuildaKitTests/Migration/Buildasaur-format-2-example2/ServerConfigs.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "password" : "SuperSecretPa55word", 4 | "id" : "D143B09C-CB1B-4831-8BA1-E2F8AB039B56", 5 | "host" : "https:\/\/127.0.0.1", 6 | "user" : "testadmin1" 7 | }, 8 | { 9 | "password" : "SuperSecretPa55word77878787", 10 | "id" : "76826238-64AE-4F48-B4B8-52A6B7A65EE4", 11 | "host" : "https:\/\/localhost", 12 | "user" : "testadmin8" 13 | } 14 | ] -------------------------------------------------------------------------------- /BuildaKitTests/Migration/Buildasaur-format-2-example2/Syncers.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "preferred_template_ref" : "B31C6530-BB84-4A93-B08C-54074AAB5F37", 4 | "id" : "564C267D-FF06-4008-9EF6-66B3AC1A3BDE", 5 | "sync_interval" : 19, 6 | "server_ref" : "D143B09C-CB1B-4831-8BA1-E2F8AB039B56", 7 | "wait_for_lttm" : false, 8 | "watched_branches" : [ 9 | "master" 10 | ], 11 | "project_ref" : "4E8E7708-01FB-448A-B929-A54887CC5857", 12 | "post_status_comments" : false 13 | }, 14 | { 15 | "preferred_template_ref" : "9B53CC35-57DA-4DA0-9B85-05FCF109512A", 16 | "id" : "B3A35C28-2176-4D88-8F60-5C769AEDBB2E", 17 | "sync_interval" : 15, 18 | "server_ref" : "76826238-64AE-4F48-B4B8-52A6B7A65EE4", 19 | "wait_for_lttm" : false, 20 | "watched_branches" : [ 21 | "master" 22 | ], 23 | "project_ref" : "D316F062-7FEC-497A-B20E-6776AA413009", 24 | "post_status_comments" : false 25 | } 26 | ] -------------------------------------------------------------------------------- /BuildaKitTests/Migration/Buildasaur-format-2-example2/Triggers/4E66D0D5-D5CC-417E-A40E-73B513CE4E10.json: -------------------------------------------------------------------------------- 1 | { 2 | "phase" : 2, 3 | "id" : "4E66D0D5-D5CC-417E-A40E-73B513CE4E10", 4 | "scriptBody" : "", 5 | "conditions" : { 6 | "status" : 2, 7 | "onWarnings" : true, 8 | "onBuildErrors" : true, 9 | "onInternalErrors" : true, 10 | "onAnalyzerWarnings" : true, 11 | "onFailingTests" : true, 12 | "onSuccess" : true 13 | }, 14 | "type" : 2, 15 | "name" : "Postbuild Email", 16 | "emailConfiguration" : { 17 | "includeCommitMessages" : true, 18 | "additionalRecipients" : [ 19 | "h@d.com", 20 | "yo@ma.lo" 21 | ], 22 | "emailCommitters" : false, 23 | "includeIssueDetails" : true 24 | } 25 | } -------------------------------------------------------------------------------- /BuildaKitTests/Migration/Buildasaur-format-2-example2/Triggers/E8F5285A-A262-4630-AF7B-236772B75760.json: -------------------------------------------------------------------------------- 1 | { 2 | "phase" : 1, 3 | "scriptBody" : "echo \"hello\"\n", 4 | "id" : "E8F5285A-A262-4630-AF7B-236772B75760", 5 | "type" : 1, 6 | "name" : "Prebuild Script" 7 | } -------------------------------------------------------------------------------- /BuildaKitTests/Migration/Buildasaur-format-3-example1/BuildTemplates/9B53CC35-57DA-4DA0-9B85-05FCF109512A.json: -------------------------------------------------------------------------------- 1 | { 2 | "id" : "9B53CC35-57DA-4DA0-9B85-05FCF109512A", 3 | "project_name" : "Buildasaur", 4 | "schedule" : { 5 | "weeklyScheduleDay" : 0, 6 | "periodicScheduleInterval" : 0, 7 | "hourOfIntegration" : 0, 8 | "minutesAfterHourToIntegrate" : 0, 9 | "scheduleType" : 3 10 | }, 11 | "triggers" : [ 12 | "E8F5285A-A262-4630-AF7B-236772B75760", 13 | "4E66D0D5-D5CC-417E-A40E-73B513CE4E10" 14 | ], 15 | "should_analyze" : true, 16 | "platform_type" : "com.apple.platform.macosx", 17 | "scheme" : "Buildasaur", 18 | "device_filter" : 0, 19 | "cleaning_policy" : 0, 20 | "testing_devices" : [ 21 | 22 | ], 23 | "should_archive" : false, 24 | "name" : "Buildasaur PR", 25 | "should_test" : true 26 | } -------------------------------------------------------------------------------- /BuildaKitTests/Migration/Buildasaur-format-3-example1/BuildTemplates/B31C6530-BB84-4A93-B08C-54074AAB5F37.json: -------------------------------------------------------------------------------- 1 | { 2 | "id" : "B31C6530-BB84-4A93-B08C-54074AAB5F37", 3 | "project_name" : "Buildasaur-Tester", 4 | "schedule" : { 5 | "weeklyScheduleDay" : 0, 6 | "periodicScheduleInterval" : 0, 7 | "hourOfIntegration" : 0, 8 | "minutesAfterHourToIntegrate" : 0, 9 | "scheduleType" : 3 10 | }, 11 | "triggers" : [ 12 | "E8F5285A-A262-4630-AF7B-236772B75760", 13 | "4E66D0D5-D5CC-417E-A40E-73B513CE4E10" 14 | ], 15 | "should_analyze" : true, 16 | "platform_type" : "com.apple.platform.iphoneos", 17 | "scheme" : "Buildasaur-Tester", 18 | "device_filter" : 0, 19 | "cleaning_policy" : 0, 20 | "testing_devices" : [ 21 | 22 | ], 23 | "should_archive" : false, 24 | "name" : "BuildaTest PR", 25 | "should_test" : true 26 | } -------------------------------------------------------------------------------- /BuildaKitTests/Migration/Buildasaur-format-3-example1/Config.json: -------------------------------------------------------------------------------- 1 | { 2 | "persistence_version" : 3 3 | } -------------------------------------------------------------------------------- /BuildaKitTests/Migration/Buildasaur-format-3-example1/Projects.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "ssh_private_key_url" : "\/Users\/honzadvorsky\/.ssh\/id_rsa", 4 | "ssh_public_key_url" : "\/Users\/honzadvorsky\/.ssh\/id_rsa.pub", 5 | "id" : "4E8E7708-01FB-448A-B929-A54887CC5857", 6 | "url" : "\/Users\/honzadvorsky\/Documents\/Buildasaur-Tester\/Buildasaur-Tester.xcodeproj" 7 | }, 8 | { 9 | "ssh_private_key_url" : "\/Users\/honzadvorsky\/.ssh\/id_rsa", 10 | "ssh_public_key_url" : "\/Users\/honzadvorsky\/.ssh\/id_rsa.pub", 11 | "id" : "D316F062-7FEC-497A-B20E-6776AA413009", 12 | "url" : "\/Users\/honzadvorsky\/Documents\/Buildasaur\/Buildasaur.xcodeproj" 13 | } 14 | ] -------------------------------------------------------------------------------- /BuildaKitTests/Migration/Buildasaur-format-3-example1/ServerConfigs.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id" : "D143B09C-CB1B-4831-8BA1-E2F8AB039B56", 4 | "host" : "https:\/\/127.0.0.1", 5 | "user" : "testadmin1" 6 | }, 7 | { 8 | "id" : "76826238-64AE-4F48-B4B8-52A6B7A65EE4", 9 | "host" : "https:\/\/localhost", 10 | "user" : "testadmin8" 11 | } 12 | ] -------------------------------------------------------------------------------- /BuildaKitTests/Migration/Buildasaur-format-3-example1/Syncers.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "preferred_template_ref" : "B31C6530-BB84-4A93-B08C-54074AAB5F37", 4 | "id" : "564C267D-FF06-4008-9EF6-66B3AC1A3BDE", 5 | "sync_interval" : 19, 6 | "server_ref" : "D143B09C-CB1B-4831-8BA1-E2F8AB039B56", 7 | "wait_for_lttm" : false, 8 | "watched_branches" : [ 9 | "master" 10 | ], 11 | "project_ref" : "4E8E7708-01FB-448A-B929-A54887CC5857", 12 | "post_status_comments" : false 13 | }, 14 | { 15 | "preferred_template_ref" : "9B53CC35-57DA-4DA0-9B85-05FCF109512A", 16 | "id" : "B3A35C28-2176-4D88-8F60-5C769AEDBB2E", 17 | "sync_interval" : 15, 18 | "server_ref" : "76826238-64AE-4F48-B4B8-52A6B7A65EE4", 19 | "wait_for_lttm" : false, 20 | "watched_branches" : [ 21 | "master" 22 | ], 23 | "project_ref" : "D316F062-7FEC-497A-B20E-6776AA413009", 24 | "post_status_comments" : false 25 | } 26 | ] -------------------------------------------------------------------------------- /BuildaKitTests/Migration/Buildasaur-format-3-example1/Triggers/4E66D0D5-D5CC-417E-A40E-73B513CE4E10.json: -------------------------------------------------------------------------------- 1 | { 2 | "phase" : 2, 3 | "id" : "4E66D0D5-D5CC-417E-A40E-73B513CE4E10", 4 | "scriptBody" : "", 5 | "conditions" : { 6 | "status" : 2, 7 | "onWarnings" : true, 8 | "onBuildErrors" : true, 9 | "onInternalErrors" : true, 10 | "onAnalyzerWarnings" : true, 11 | "onFailingTests" : true, 12 | "onSuccess" : true 13 | }, 14 | "type" : 2, 15 | "name" : "Postbuild Email", 16 | "emailConfiguration" : { 17 | "includeCommitMessages" : true, 18 | "additionalRecipients" : [ 19 | "h@d.com", 20 | "yo@ma.lo" 21 | ], 22 | "emailCommitters" : false, 23 | "includeIssueDetails" : true 24 | } 25 | } -------------------------------------------------------------------------------- /BuildaKitTests/Migration/Buildasaur-format-3-example1/Triggers/E8F5285A-A262-4630-AF7B-236772B75760.json: -------------------------------------------------------------------------------- 1 | { 2 | "phase" : 1, 3 | "scriptBody" : "echo \"hello\"\n", 4 | "id" : "E8F5285A-A262-4630-AF7B-236772B75760", 5 | "type" : 1, 6 | "name" : "Prebuild Script" 7 | } -------------------------------------------------------------------------------- /BuildaKitTests/Migration/Buildasaur-format-4-example1/BuildTemplates/9B53CC35-57DA-4DA0-9B85-05FCF109512A.json: -------------------------------------------------------------------------------- 1 | { 2 | "id" : "9B53CC35-57DA-4DA0-9B85-05FCF109512A", 3 | "project_name" : "Buildasaur", 4 | "schedule" : { 5 | "weeklyScheduleDay" : 0, 6 | "periodicScheduleInterval" : 0, 7 | "hourOfIntegration" : 0, 8 | "minutesAfterHourToIntegrate" : 0, 9 | "scheduleType" : 3 10 | }, 11 | "triggers" : [ 12 | "E8F5285A-A262-4630-AF7B-236772B75760", 13 | "4E66D0D5-D5CC-417E-A40E-73B513CE4E10" 14 | ], 15 | "should_analyze" : true, 16 | "platform_type" : "com.apple.platform.macosx", 17 | "scheme" : "Buildasaur", 18 | "device_filter" : 0, 19 | "cleaning_policy" : 0, 20 | "testing_devices" : [ 21 | 22 | ], 23 | "should_archive" : false, 24 | "name" : "Buildasaur PR", 25 | "should_test" : true 26 | } -------------------------------------------------------------------------------- /BuildaKitTests/Migration/Buildasaur-format-4-example1/BuildTemplates/B31C6530-BB84-4A93-B08C-54074AAB5F37.json: -------------------------------------------------------------------------------- 1 | { 2 | "id" : "B31C6530-BB84-4A93-B08C-54074AAB5F37", 3 | "project_name" : "Buildasaur-Tester", 4 | "schedule" : { 5 | "weeklyScheduleDay" : 0, 6 | "periodicScheduleInterval" : 0, 7 | "hourOfIntegration" : 0, 8 | "minutesAfterHourToIntegrate" : 0, 9 | "scheduleType" : 3 10 | }, 11 | "triggers" : [ 12 | "E8F5285A-A262-4630-AF7B-236772B75760", 13 | "4E66D0D5-D5CC-417E-A40E-73B513CE4E10" 14 | ], 15 | "should_analyze" : true, 16 | "platform_type" : "com.apple.platform.iphoneos", 17 | "scheme" : "Buildasaur-Tester", 18 | "device_filter" : 0, 19 | "cleaning_policy" : 0, 20 | "testing_devices" : [ 21 | 22 | ], 23 | "should_archive" : false, 24 | "name" : "BuildaTest PR", 25 | "should_test" : true 26 | } -------------------------------------------------------------------------------- /BuildaKitTests/Migration/Buildasaur-format-4-example1/Config.json: -------------------------------------------------------------------------------- 1 | { 2 | "persistence_version" : 4 3 | } -------------------------------------------------------------------------------- /BuildaKitTests/Migration/Buildasaur-format-4-example1/Projects.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "ssh_public_key_url" : "\/Users\/honzadvorsky\/.ssh\/id_rsa.pub", 4 | "id" : "4E8E7708-01FB-448A-B929-A54887CC5857", 5 | "url" : "\/Users\/honzadvorsky\/Documents\/Buildasaur-Tester\/Buildasaur-Tester.xcodeproj", 6 | "ssh_private_key_url" : "\/Users\/honzadvorsky\/.ssh\/id_rsa" 7 | }, 8 | { 9 | "ssh_public_key_url" : "\/Users\/honzadvorsky\/.ssh\/id_rsa.pub", 10 | "id" : "D316F062-7FEC-497A-B20E-6776AA413009", 11 | "url" : "\/Users\/honzadvorsky\/Documents\/Buildasaur\/Buildasaur.xcodeproj", 12 | "ssh_private_key_url" : "\/Users\/honzadvorsky\/.ssh\/id_rsa" 13 | } 14 | ] -------------------------------------------------------------------------------- /BuildaKitTests/Migration/Buildasaur-format-4-example1/ServerConfigs.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id" : "D143B09C-CB1B-4831-8BA1-E2F8AB039B56", 4 | "host" : "https:\/\/127.0.0.1", 5 | "user" : "testadmin1" 6 | }, 7 | { 8 | "id" : "76826238-64AE-4F48-B4B8-52A6B7A65EE4", 9 | "host" : "https:\/\/localhost", 10 | "user" : "testadmin8" 11 | } 12 | ] -------------------------------------------------------------------------------- /BuildaKitTests/Migration/Buildasaur-format-4-example1/Syncers.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "preferred_template_ref" : "B31C6530-BB84-4A93-B08C-54074AAB5F37", 4 | "id" : "564C267D-FF06-4008-9EF6-66B3AC1A3BDE", 5 | "watching_branches" : { 6 | "master" : true 7 | }, 8 | "sync_interval" : 19, 9 | "automatically_watch_new_branches" : false, 10 | "server_ref" : "D143B09C-CB1B-4831-8BA1-E2F8AB039B56", 11 | "wait_for_lttm" : false, 12 | "project_ref" : "4E8E7708-01FB-448A-B929-A54887CC5857", 13 | "post_status_comments" : false 14 | }, 15 | { 16 | "preferred_template_ref" : "9B53CC35-57DA-4DA0-9B85-05FCF109512A", 17 | "id" : "B3A35C28-2176-4D88-8F60-5C769AEDBB2E", 18 | "watching_branches" : { 19 | "master" : true 20 | }, 21 | "sync_interval" : 15, 22 | "automatically_watch_new_branches" : false, 23 | "server_ref" : "76826238-64AE-4F48-B4B8-52A6B7A65EE4", 24 | "wait_for_lttm" : false, 25 | "project_ref" : "D316F062-7FEC-497A-B20E-6776AA413009", 26 | "post_status_comments" : false 27 | } 28 | ] -------------------------------------------------------------------------------- /BuildaKitTests/Migration/Buildasaur-format-4-example1/Triggers/4E66D0D5-D5CC-417E-A40E-73B513CE4E10.json: -------------------------------------------------------------------------------- 1 | { 2 | "phase" : 2, 3 | "id" : "4E66D0D5-D5CC-417E-A40E-73B513CE4E10", 4 | "scriptBody" : "", 5 | "conditions" : { 6 | "status" : 2, 7 | "onWarnings" : true, 8 | "onBuildErrors" : true, 9 | "onInternalErrors" : true, 10 | "onAnalyzerWarnings" : true, 11 | "onFailingTests" : true, 12 | "onSuccess" : true 13 | }, 14 | "type" : 2, 15 | "name" : "Postbuild Email", 16 | "emailConfiguration" : { 17 | "includeCommitMessages" : true, 18 | "additionalRecipients" : [ 19 | "h@d.com", 20 | "yo@ma.lo" 21 | ], 22 | "emailCommitters" : false, 23 | "includeIssueDetails" : true 24 | } 25 | } -------------------------------------------------------------------------------- /BuildaKitTests/Migration/Buildasaur-format-4-example1/Triggers/E8F5285A-A262-4630-AF7B-236772B75760.json: -------------------------------------------------------------------------------- 1 | { 2 | "phase" : 1, 3 | "scriptBody" : "echo \"hello\"\n", 4 | "id" : "E8F5285A-A262-4630-AF7B-236772B75760", 5 | "type" : 1, 6 | "name" : "Prebuild Script" 7 | } -------------------------------------------------------------------------------- /BuildaKitTests/MockHelpers.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MockHelpers.swift 3 | // Buildasaur 4 | // 5 | // Created by Honza Dvorsky on 17/05/2015. 6 | // Copyright (c) 2015 Honza Dvorsky. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | class MockHelpers { 12 | 13 | class func loadSampleIntegration() -> NSMutableDictionary { 14 | 15 | let bundle = Bundle(for: MockHelpers.self) 16 | if let url = bundle.url(forResource: "sampleFinishedIntegration", withExtension: "json") { 17 | let data = try! Data(contentsOf: url) 18 | if let obj = try! JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? NSMutableDictionary { 19 | return obj 20 | } 21 | } else { 22 | assertionFailure("no sample integration json") 23 | } 24 | return NSMutableDictionary() 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /BuildaKitTests/TestProjects/Buildasaur-TestProject-iOS/Buildasaur-TestProject-iOS.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /BuildaKitTests/TestProjects/Buildasaur-TestProject-iOS/Buildasaur-TestProject-iOS.xcodeproj/xcshareddata/Buildasaur-TestProject-iOS.xcscmblueprint: -------------------------------------------------------------------------------- 1 | { 2 | "DVTSourceControlWorkspaceBlueprintPrimaryRemoteRepositoryKey" : "1C5C2A17EEADA6DBF6678501245487A71FBE28BB", 3 | "DVTSourceControlWorkspaceBlueprintWorkingCopyRepositoryLocationsKey" : { 4 | 5 | }, 6 | "DVTSourceControlWorkspaceBlueprintWorkingCopyStatesKey" : { 7 | "1C5C2A17EEADA6DBF6678501245487A71FBE28BB" : 0 8 | }, 9 | "DVTSourceControlWorkspaceBlueprintIdentifierKey" : "F1644247-F0D5-4765-AF88-C8B0AB50ABFB", 10 | "DVTSourceControlWorkspaceBlueprintWorkingCopyPathsKey" : { 11 | "1C5C2A17EEADA6DBF6678501245487A71FBE28BB" : "Buildasaur/" 12 | }, 13 | "DVTSourceControlWorkspaceBlueprintNameKey" : "Buildasaur-TestProject-iOS", 14 | "DVTSourceControlWorkspaceBlueprintVersion" : 204, 15 | "DVTSourceControlWorkspaceBlueprintRelativePathToProjectKey" : "BuildaKitTests/TestProjects/Buildasaur-TestProject-iOS/Buildasaur-TestProject-iOS.xcodeproj", 16 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoriesKey" : [ 17 | { 18 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "github.com:czechboy0\/Buildasaur.git", 19 | "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git", 20 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "1C5C2A17EEADA6DBF6678501245487A71FBE28BB" 21 | } 22 | ] 23 | } -------------------------------------------------------------------------------- /BuildaKitTests/TestProjects/Buildasaur-TestProject-iOS/Buildasaur-TestProject-iOS.xcodeproj/xcshareddata/xcschemes/Buildasaur-TestProject-iOS.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /BuildaKitTests/TestProjects/Buildasaur-TestProject-iOS/Buildasaur-TestProject-iOS.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /BuildaKitTests/TestProjects/Buildasaur-TestProject-iOS/Buildasaur-TestProject-iOS.xcworkspace/xcshareddata/Buildasaur-TestProject-iOS.xcscmblueprint: -------------------------------------------------------------------------------- 1 | { 2 | "DVTSourceControlWorkspaceBlueprintPrimaryRemoteRepositoryKey" : "1C5C2A17EEADA6DBF6678501245487A71FBE28BB", 3 | "DVTSourceControlWorkspaceBlueprintWorkingCopyRepositoryLocationsKey" : { 4 | 5 | }, 6 | "DVTSourceControlWorkspaceBlueprintWorkingCopyStatesKey" : { 7 | "1C5C2A17EEADA6DBF6678501245487A71FBE28BB" : 0 8 | }, 9 | "DVTSourceControlWorkspaceBlueprintIdentifierKey" : "F1644247-F0D5-4765-AF88-C8B0AB50ABFB", 10 | "DVTSourceControlWorkspaceBlueprintWorkingCopyPathsKey" : { 11 | "1C5C2A17EEADA6DBF6678501245487A71FBE28BB" : "Buildasaur/" 12 | }, 13 | "DVTSourceControlWorkspaceBlueprintNameKey" : "Buildasaur-TestProject-iOS", 14 | "DVTSourceControlWorkspaceBlueprintVersion" : 204, 15 | "DVTSourceControlWorkspaceBlueprintRelativePathToProjectKey" : "BuildaKitTests/TestProjects/Buildasaur-TestProject-iOS/Buildasaur-TestProject-iOS.xcworkspace", 16 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoriesKey" : [ 17 | { 18 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "github.com:czechboy0\/Buildasaur.git", 19 | "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git", 20 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "1C5C2A17EEADA6DBF6678501245487A71FBE28BB" 21 | } 22 | ] 23 | } -------------------------------------------------------------------------------- /BuildaKitTests/TestProjects/Buildasaur-TestProject-iOS/Buildasaur-TestProject-iOS/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // Buildasaur-TestProject-iOS 4 | // 5 | // Created by Honza Dvorsky on 10/11/15. 6 | // Copyright © 2015 Honza Dvorsky. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { 17 | // Override point for customization after application launch. 18 | return true 19 | } 20 | 21 | func applicationWillResignActive(application: UIApplication) { 22 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 23 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 24 | } 25 | 26 | func applicationDidEnterBackground(application: UIApplication) { 27 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 28 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 29 | } 30 | 31 | func applicationWillEnterForeground(application: UIApplication) { 32 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 33 | } 34 | 35 | func applicationDidBecomeActive(application: UIApplication) { 36 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 37 | } 38 | 39 | func applicationWillTerminate(application: UIApplication) { 40 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /BuildaKitTests/TestProjects/Buildasaur-TestProject-iOS/Buildasaur-TestProject-iOS/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "ipad", 35 | "size" : "29x29", 36 | "scale" : "1x" 37 | }, 38 | { 39 | "idiom" : "ipad", 40 | "size" : "29x29", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "40x40", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "40x40", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "76x76", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "76x76", 61 | "scale" : "2x" 62 | } 63 | ], 64 | "info" : { 65 | "version" : 1, 66 | "author" : "xcode" 67 | } 68 | } -------------------------------------------------------------------------------- /BuildaKitTests/TestProjects/Buildasaur-TestProject-iOS/Buildasaur-TestProject-iOS/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /BuildaKitTests/TestProjects/Buildasaur-TestProject-iOS/Buildasaur-TestProject-iOS/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /BuildaKitTests/TestProjects/Buildasaur-TestProject-iOS/Buildasaur-TestProject-iOS/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationPortrait 36 | UIInterfaceOrientationLandscapeLeft 37 | UIInterfaceOrientationLandscapeRight 38 | 39 | UISupportedInterfaceOrientations~ipad 40 | 41 | UIInterfaceOrientationPortrait 42 | UIInterfaceOrientationPortraitUpsideDown 43 | UIInterfaceOrientationLandscapeLeft 44 | UIInterfaceOrientationLandscapeRight 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /BuildaKitTests/TestProjects/Buildasaur-TestProject-iOS/Buildasaur-TestProject-iOS/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // Buildasaur-TestProject-iOS 4 | // 5 | // Created by Honza Dvorsky on 10/11/15. 6 | // Copyright © 2015 Honza Dvorsky. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ViewController: UIViewController { 12 | 13 | override func viewDidLoad() { 14 | super.viewDidLoad() 15 | // Do any additional setup after loading the view, typically from a nib. 16 | } 17 | 18 | override func didReceiveMemoryWarning() { 19 | super.didReceiveMemoryWarning() 20 | // Dispose of any resources that can be recreated. 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /BuildaKitTests/WorkspaceMetadataTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WorkspaceMetadataTests.swift 3 | // Buildasaur 4 | // 5 | // Created by Isaac Overacker on 5/6/16. 6 | // Copyright © 2016 Honza Dvorsky. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | import Nimble 11 | @testable import BuildaKit 12 | import BuildaGitServer 13 | 14 | class WorkspaceMetadataTests: XCTestCase { 15 | 16 | func help_test_parse_with(urlString url: String, expectedCheckoutType: CheckoutType, expectedGitService: GitService) { 17 | guard let (checkoutType, service) = WorkspaceMetadata.parse(projectURLString: url) else { 18 | XCTFail("Failed to parse URL string: \(url)") 19 | return 20 | } 21 | 22 | expect(checkoutType) == expectedCheckoutType 23 | expect(service) == expectedGitService 24 | } 25 | 26 | // MARK: GitHub 27 | 28 | func test_parse_SSH_withSlash_github() { 29 | help_test_parse_with(urlString: "ssh://git@github.com/organization/repo", 30 | expectedCheckoutType: CheckoutType.SSH, 31 | expectedGitService: GitService.GitHub) 32 | } 33 | 34 | func test_parse_noSSH_withColon_github() { 35 | help_test_parse_with(urlString: "git@github.com:organization/repo", 36 | expectedCheckoutType: CheckoutType.SSH, 37 | expectedGitService: GitService.GitHub) 38 | } 39 | 40 | // MARK: HTTP 41 | 42 | func test_parse_HTTPS() { 43 | expect(WorkspaceMetadata.parse(projectURLString: "https://github.com/organization/repo")).to(beNil()) 44 | } 45 | 46 | func test_parse_HTTP() { 47 | expect(WorkspaceMetadata.parse(projectURLString: "http://github.com/organization/repo")).to(beNil()) 48 | } 49 | 50 | func test_parse_implicitHTTP() { 51 | expect(WorkspaceMetadata.parse(projectURLString: "github.com/organization/repo")).to(beNil()) 52 | } 53 | 54 | // MARK: Git protocol 55 | 56 | func test_parse_git() { 57 | expect(WorkspaceMetadata.parse(projectURLString: "git://github.com/organization/repo")).to(beNil()) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /Buildasaur.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Buildasaur.xcodeproj/xcshareddata/xcschemes/BuildaKit.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 32 | 33 | 34 | 35 | 36 | 37 | 48 | 49 | 55 | 56 | 57 | 58 | 59 | 60 | 66 | 67 | 73 | 74 | 75 | 76 | 78 | 79 | 82 | 83 | 84 | -------------------------------------------------------------------------------- /Buildasaur.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Buildasaur.xcworkspace/xcshareddata/Buildasaur.xccheckout: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDESourceControlProjectFavoriteDictionaryKey 6 | 7 | IDESourceControlProjectIdentifier 8 | 69B0DA2F-5930-4961-9871-EF4103C70CA9 9 | IDESourceControlProjectName 10 | Buildasaur 11 | IDESourceControlProjectOriginsDictionary 12 | 13 | 1C5C2A17EEADA6DBF6678501245487A71FBE28BB 14 | github.com:czechboy0/Buildasaur.git 15 | 16 | IDESourceControlProjectPath 17 | Buildasaur.xcworkspace 18 | IDESourceControlProjectRelativeInstallPathDictionary 19 | 20 | 1C5C2A17EEADA6DBF6678501245487A71FBE28BB 21 | .. 22 | 23 | IDESourceControlProjectURL 24 | github.com:czechboy0/Buildasaur.git 25 | IDESourceControlProjectVersion 26 | 111 27 | IDESourceControlProjectWCCIdentifier 28 | 1C5C2A17EEADA6DBF6678501245487A71FBE28BB 29 | IDESourceControlProjectWCConfigurations 30 | 31 | 32 | IDESourceControlRepositoryExtensionIdentifierKey 33 | public.vcs.git 34 | IDESourceControlWCCIdentifierKey 35 | 1C5C2A17EEADA6DBF6678501245487A71FBE28BB 36 | IDESourceControlWCCName 37 | Buildasaur 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /Buildasaur.xcworkspace/xcshareddata/Buildasaur.xcscmblueprint: -------------------------------------------------------------------------------- 1 | { 2 | "DVTSourceControlWorkspaceBlueprintPrimaryRemoteRepositoryKey" : "1C5C2A17EEADA6DBF6678501245487A71FBE28BB", 3 | "DVTSourceControlWorkspaceBlueprintWorkingCopyRepositoryLocationsKey" : { 4 | 5 | }, 6 | "DVTSourceControlWorkspaceBlueprintWorkingCopyStatesKey" : { 7 | "1C5C2A17EEADA6DBF6678501245487A71FBE28BB" : 0 8 | }, 9 | "DVTSourceControlWorkspaceBlueprintIdentifierKey" : "69B0DA2F-5930-4961-9871-EF4103C70CA9", 10 | "DVTSourceControlWorkspaceBlueprintWorkingCopyPathsKey" : { 11 | "1C5C2A17EEADA6DBF6678501245487A71FBE28BB" : "Buildasaur" 12 | }, 13 | "DVTSourceControlWorkspaceBlueprintNameKey" : "Buildasaur", 14 | "DVTSourceControlWorkspaceBlueprintVersion" : 203, 15 | "DVTSourceControlWorkspaceBlueprintRelativePathToProjectKey" : "Buildasaur.xcworkspace", 16 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoriesKey" : [ 17 | { 18 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "github.com:czechboy0\/Buildasaur.git", 19 | "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git", 20 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "1C5C2A17EEADA6DBF6678501245487A71FBE28BB" 21 | } 22 | ] 23 | } -------------------------------------------------------------------------------- /Buildasaur/Authentication/ServiceAuthentication.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ServiceAuthentication.swift 3 | // Buildasaur 4 | // 5 | // Created by Honza Dvorsky on 1/26/16. 6 | // Copyright © 2016 Honza Dvorsky. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import OAuthSwift 11 | import BuildaGitServer 12 | 13 | class ServiceAuthenticator { 14 | 15 | private var oauth: OAuth2Swift? 16 | 17 | enum ParamKey: String { 18 | case ConsumerId 19 | case ConsumerSecret 20 | case AuthorizeUrl 21 | case AccessTokenUrl 22 | case ResponseType 23 | case CallbackUrl 24 | case Scope 25 | case State 26 | } 27 | 28 | typealias SecretFromResponseParams = ([String: String]) -> String 29 | 30 | init() {} 31 | 32 | func handleUrl(_ url: URL) { 33 | OAuthSwift.handle(url: url) 34 | } 35 | 36 | func getAccess(_ service: GitService, completion: @escaping (_ auth: ProjectAuthenticator?, _ error: Error?) -> Void) { 37 | 38 | let (params, secretFromResponseParams) = self.paramsForService(service) 39 | 40 | self.oauth = OAuth2Swift( 41 | consumerKey: params[.ConsumerId]!, 42 | consumerSecret: params[.ConsumerSecret]!, 43 | authorizeUrl: params[.AuthorizeUrl]!, 44 | accessTokenUrl: params[.AccessTokenUrl]!, 45 | responseType: params[.ResponseType]! 46 | ) 47 | self.oauth?.authorize(withCallbackURL: 48 | URL(string: params[.CallbackUrl]!)!, 49 | scope: params[.Scope]!, 50 | state: params[.State]!, 51 | success: { _, _, parameters in 52 | 53 | let secret = secretFromResponseParams(parameters as! [String : String]) 54 | let auth = ProjectAuthenticator(service: service, username: "GIT", type: .OAuthToken, secret: secret) 55 | completion(auth, nil) 56 | }, 57 | failure: { error in 58 | completion(nil, error) 59 | } 60 | ) 61 | } 62 | 63 | func getAccessTokenFromRefresh(_ service: GitService, refreshToken: String, completion: (auth: ProjectAuthenticator?, error: Error?)) { 64 | //TODO: implement refresh token flow - to get and save a new access token 65 | } 66 | 67 | private func paramsForService(_ service: GitService) -> ([ParamKey: String], SecretFromResponseParams) { 68 | switch service { 69 | case .GitHub: 70 | return self.getGitHubParameters() 71 | } 72 | } 73 | 74 | private func getGitHubParameters() -> ([ParamKey: String], SecretFromResponseParams) { 75 | let service = GitService.GitHub 76 | let params: [ParamKey: String] = [ 77 | .ConsumerId: service.serviceKey(), 78 | .ConsumerSecret: service.serviceSecret(), 79 | .AuthorizeUrl: service.authorizeUrl(), 80 | .AccessTokenUrl: service.accessTokenUrl(), 81 | .ResponseType: "code", 82 | .CallbackUrl: "buildasaur://oauth-callback/github", 83 | .Scope: "repo", 84 | .State: generateState(withLength: 20) as String 85 | ] 86 | let secret: SecretFromResponseParams = { 87 | //just pull out the access token, that's all we need 88 | return $0["access_token"]! 89 | } 90 | return (params, secret) 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /Buildasaur/Buildasaur.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /Buildasaur/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "16x16", 5 | "idiom" : "mac", 6 | "filename" : "Icon_16.png", 7 | "scale" : "1x" 8 | }, 9 | { 10 | "size" : "16x16", 11 | "idiom" : "mac", 12 | "filename" : "Icon_16@2x.png", 13 | "scale" : "2x" 14 | }, 15 | { 16 | "size" : "32x32", 17 | "idiom" : "mac", 18 | "filename" : "Icon_32.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "32x32", 23 | "idiom" : "mac", 24 | "filename" : "Icon_32@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "128x128", 29 | "idiom" : "mac", 30 | "filename" : "Icon_128.png", 31 | "scale" : "1x" 32 | }, 33 | { 34 | "size" : "128x128", 35 | "idiom" : "mac", 36 | "filename" : "Icon_128@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "256x256", 41 | "idiom" : "mac", 42 | "filename" : "Icon_256.png", 43 | "scale" : "1x" 44 | }, 45 | { 46 | "size" : "256x256", 47 | "idiom" : "mac", 48 | "filename" : "Icon_256@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "512x512", 53 | "idiom" : "mac", 54 | "filename" : "Icon_512.png", 55 | "scale" : "1x" 56 | }, 57 | { 58 | "size" : "512x512", 59 | "idiom" : "mac", 60 | "filename" : "Icon_512@2x.png", 61 | "scale" : "2x" 62 | } 63 | ], 64 | "info" : { 65 | "version" : 1, 66 | "author" : "xcode" 67 | } 68 | } -------------------------------------------------------------------------------- /Buildasaur/Images.xcassets/AppIcon.appiconset/Icon_128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s-faychatelard/Buildasaur/85ec3db576f5995e69d0974b6f650be6d0c912ee/Buildasaur/Images.xcassets/AppIcon.appiconset/Icon_128.png -------------------------------------------------------------------------------- /Buildasaur/Images.xcassets/AppIcon.appiconset/Icon_128@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s-faychatelard/Buildasaur/85ec3db576f5995e69d0974b6f650be6d0c912ee/Buildasaur/Images.xcassets/AppIcon.appiconset/Icon_128@2x.png -------------------------------------------------------------------------------- /Buildasaur/Images.xcassets/AppIcon.appiconset/Icon_16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s-faychatelard/Buildasaur/85ec3db576f5995e69d0974b6f650be6d0c912ee/Buildasaur/Images.xcassets/AppIcon.appiconset/Icon_16.png -------------------------------------------------------------------------------- /Buildasaur/Images.xcassets/AppIcon.appiconset/Icon_16@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s-faychatelard/Buildasaur/85ec3db576f5995e69d0974b6f650be6d0c912ee/Buildasaur/Images.xcassets/AppIcon.appiconset/Icon_16@2x.png -------------------------------------------------------------------------------- /Buildasaur/Images.xcassets/AppIcon.appiconset/Icon_256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s-faychatelard/Buildasaur/85ec3db576f5995e69d0974b6f650be6d0c912ee/Buildasaur/Images.xcassets/AppIcon.appiconset/Icon_256.png -------------------------------------------------------------------------------- /Buildasaur/Images.xcassets/AppIcon.appiconset/Icon_256@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s-faychatelard/Buildasaur/85ec3db576f5995e69d0974b6f650be6d0c912ee/Buildasaur/Images.xcassets/AppIcon.appiconset/Icon_256@2x.png -------------------------------------------------------------------------------- /Buildasaur/Images.xcassets/AppIcon.appiconset/Icon_32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s-faychatelard/Buildasaur/85ec3db576f5995e69d0974b6f650be6d0c912ee/Buildasaur/Images.xcassets/AppIcon.appiconset/Icon_32.png -------------------------------------------------------------------------------- /Buildasaur/Images.xcassets/AppIcon.appiconset/Icon_32@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s-faychatelard/Buildasaur/85ec3db576f5995e69d0974b6f650be6d0c912ee/Buildasaur/Images.xcassets/AppIcon.appiconset/Icon_32@2x.png -------------------------------------------------------------------------------- /Buildasaur/Images.xcassets/AppIcon.appiconset/Icon_512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s-faychatelard/Buildasaur/85ec3db576f5995e69d0974b6f650be6d0c912ee/Buildasaur/Images.xcassets/AppIcon.appiconset/Icon_512.png -------------------------------------------------------------------------------- /Buildasaur/Images.xcassets/AppIcon.appiconset/Icon_512@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s-faychatelard/Buildasaur/85ec3db576f5995e69d0974b6f650be6d0c912ee/Buildasaur/Images.xcassets/AppIcon.appiconset/Icon_512@2x.png -------------------------------------------------------------------------------- /Buildasaur/Images.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Buildasaur/Images.xcassets/builda.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "builda1.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "builda2.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "builda3.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Buildasaur/Images.xcassets/builda.imageset/builda1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s-faychatelard/Buildasaur/85ec3db576f5995e69d0974b6f650be6d0c912ee/Buildasaur/Images.xcassets/builda.imageset/builda1.png -------------------------------------------------------------------------------- /Buildasaur/Images.xcassets/builda.imageset/builda2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s-faychatelard/Buildasaur/85ec3db576f5995e69d0974b6f650be6d0c912ee/Buildasaur/Images.xcassets/builda.imageset/builda2.png -------------------------------------------------------------------------------- /Buildasaur/Images.xcassets/builda.imageset/builda3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s-faychatelard/Buildasaur/85ec3db576f5995e69d0974b6f650be6d0c912ee/Buildasaur/Images.xcassets/builda.imageset/builda3.png -------------------------------------------------------------------------------- /Buildasaur/Images.xcassets/icon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "Icon_mono_16.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x", 11 | "filename" : "Icon_mono_16@2x.png" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x", 16 | "filename" : "Icon_mono_16@3x.png" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode", 22 | "template-rendering-intent" : "template" 23 | } 24 | } -------------------------------------------------------------------------------- /Buildasaur/Images.xcassets/icon.imageset/Icon_mono_16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s-faychatelard/Buildasaur/85ec3db576f5995e69d0974b6f650be6d0c912ee/Buildasaur/Images.xcassets/icon.imageset/Icon_mono_16.png -------------------------------------------------------------------------------- /Buildasaur/Images.xcassets/icon.imageset/Icon_mono_16@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s-faychatelard/Buildasaur/85ec3db576f5995e69d0974b6f650be6d0c912ee/Buildasaur/Images.xcassets/icon.imageset/Icon_mono_16@2x.png -------------------------------------------------------------------------------- /Buildasaur/Images.xcassets/icon.imageset/Icon_mono_16@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s-faychatelard/Buildasaur/85ec3db576f5995e69d0974b6f650be6d0c912ee/Buildasaur/Images.xcassets/icon.imageset/Icon_mono_16@3x.png -------------------------------------------------------------------------------- /Buildasaur/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 2.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleURLTypes 24 | 25 | 26 | CFBundleTypeRole 27 | Editor 28 | CFBundleURLIconFile 29 | 30 | CFBundleURLSchemes 31 | 32 | buildasaur 33 | 34 | 35 | 36 | CFBundleVersion 37 | 40 38 | Fabric 39 | 40 | APIKey 41 | 2d4be073b00aca9b29d639d82e1ccb80444e8c0c 42 | Kits 43 | 44 | 45 | KitInfo 46 | 47 | KitName 48 | Crashlytics 49 | 50 | 51 | 52 | LSMinimumSystemVersion 53 | $(MACOSX_DEPLOYMENT_TARGET) 54 | LSUIElement 55 | 56 | NSAppTransportSecurity 57 | 58 | NSAllowsArbitraryLoads 59 | 60 | 61 | NSHumanReadableCopyright 62 | Copyright © 2016 Honza Dvorsky. All rights reserved. 63 | NSMainStoryboardFile 64 | Main 65 | NSPrincipalClass 66 | NSApplication 67 | SUEnableAutomaticChecks 68 | 69 | SUFeedURL 70 | https://raw.githubusercontent.com/czechboy0/Buildasaur/master/sparkle.xml 71 | SUScheduledCheckInterval 72 | 86400 73 | 74 | 75 | -------------------------------------------------------------------------------- /Buildasaur/Utils/NSButton+OnClick.swift: -------------------------------------------------------------------------------- 1 | import AppKit 2 | 3 | extension NSButton { 4 | private struct NSButtonKeys { 5 | static var click = "NSButton_click" 6 | } 7 | 8 | @objc var onClick: ((AnyObject?) -> Void)? { 9 | get { 10 | return objc_getAssociatedObject(self, &NSButtonKeys.click) as? ((AnyObject?) -> Void) 11 | } 12 | set { 13 | if let newValue = newValue { 14 | objc_setAssociatedObject(self, 15 | &NSButtonKeys.click, 16 | newValue, 17 | .OBJC_ASSOCIATION_RETAIN_NONATOMIC) 18 | self.target = self 19 | self.action = #selector(click(_:)) 20 | } 21 | } 22 | } 23 | 24 | @objc private func click(_ sender: AnyObject?) { 25 | self.onClick?(sender) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Buildasaur/Utils/NSStepper+ValueChanged.swift: -------------------------------------------------------------------------------- 1 | import AppKit 2 | 3 | extension NSStepper { 4 | private struct NSStepperKeys { 5 | static var valueChanged = "NSStepper_valueChanged" 6 | } 7 | 8 | @objc var onValueChanged: ((Any?) -> Void)? { 9 | get { 10 | return objc_getAssociatedObject(self, &NSStepperKeys.valueChanged) as? ((Any?) -> Void) 11 | } 12 | set { 13 | if let newValue = newValue { 14 | objc_setAssociatedObject(self, 15 | &NSStepperKeys.valueChanged, 16 | newValue, 17 | .OBJC_ASSOCIATION_RETAIN_NONATOMIC) 18 | self.target = self 19 | self.action = #selector(valueChanged(_:)) 20 | } 21 | } 22 | } 23 | 24 | @objc private func valueChanged(_ sender: Any?) { 25 | self.onValueChanged?(sender) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Buildasaur/Utils/StoryboardLoadingUtils.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StoryboardLoadingUtils.swift 3 | // Buildasaur 4 | // 5 | // Created by Honza Dvorsky on 30/09/2015. 6 | // Copyright © 2015 Honza Dvorsky. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | extension NSStoryboard { 12 | static var mainStoryboard: NSStoryboard { 13 | return NSStoryboard(name: NSStoryboard.Name(rawValue: "Main"), bundle: nil) 14 | } 15 | } 16 | 17 | class StoryboardLoader { 18 | let storyboard: NSStoryboard 19 | weak var delegate: StoryboardLoaderDelegate? 20 | 21 | init(storyboard: NSStoryboard) { 22 | self.storyboard = storyboard 23 | } 24 | 25 | //for presentable view controllers 26 | func presentableViewControllerWithStoryboardIdentifier(_ storyboardIdentifier: String, uniqueIdentifier: String, delegate: PresentableViewControllerDelegate?) -> T { 27 | 28 | //look at our existing view controllers 29 | if let found = self.delegate?.storyboardLoaderExistingViewControllerWithIdentifier(uniqueIdentifier) { 30 | //we already have it live, let's reuse it 31 | return found as! T 32 | } 33 | 34 | //nope, we have to create it from storyboard 35 | guard let viewController = self.storyboard.instantiateController(withIdentifier: NSStoryboard.SceneIdentifier(rawValue: storyboardIdentifier)) as? PresentableViewController else { 36 | fatalError("Failed to instantiate View Controller with identifier \(storyboardIdentifier) as a PresentableViewController") 37 | } 38 | 39 | //asign props 40 | viewController.uniqueIdentifier = uniqueIdentifier 41 | viewController.storyboardLoader = self 42 | viewController.presentingDelegate = delegate 43 | return viewController as! T 44 | } 45 | 46 | func typedViewControllerWithStoryboardIdentifier(_ storyboardIdentifier: String) -> T { 47 | return self.viewControllerWithStoryboardIdentifier(storyboardIdentifier) as! T 48 | } 49 | 50 | //for all other view controllers 51 | func viewControllerWithStoryboardIdentifier(_ storyboardIdentifier: String) -> NSViewController { 52 | guard let viewController = self.storyboard.instantiateController(withIdentifier: NSStoryboard.SceneIdentifier(rawValue: storyboardIdentifier)) as? NSViewController else { 53 | fatalError("Failed to instantiate View Controller with identifier \(storyboardIdentifier)") 54 | } 55 | return viewController 56 | } 57 | } 58 | 59 | protocol StoryboardLoaderDelegate: class { 60 | 61 | func storyboardLoaderExistingViewControllerWithIdentifier(_ identifier: String) -> PresentableViewController? 62 | } 63 | -------------------------------------------------------------------------------- /Buildasaur/Utils/URLUtils.swift: -------------------------------------------------------------------------------- 1 | // 2 | // URLUtils.swift 3 | // Buildasaur 4 | // 5 | // Created by Honza Dvorsky on 10/13/15. 6 | // Copyright © 2015 Honza Dvorsky. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | func openLink(_ link: String) { 12 | 13 | if let url = URL(string: link) { 14 | NSWorkspace.shared.open(url) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Buildasaur/ViewControllers/Base/ConfigEditViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ConfigEditViewController.swift 3 | // Buildasaur 4 | // 5 | // Created by Honza Dvorsky on 08/03/2015. 6 | // Copyright (c) 2015 Honza Dvorsky. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | import BuildaUtils 11 | import XcodeServerSDK 12 | import BuildaKit 13 | 14 | class ConfigEditViewController: EditableViewController { 15 | 16 | var availabilityCheckState: AvailabilityCheckState = .unchecked { 17 | didSet { 18 | let imageName = ConfigEditViewController.imageNameForStatus(self.availabilityCheckState) 19 | let image = NSImage(named: NSImage.Name(rawValue: imageName)) 20 | self.serverStatusImageView.image = image 21 | 22 | if self.availabilityCheckState == .checking { 23 | self.progressIndicator?.startAnimation(nil) 24 | } else { 25 | self.progressIndicator?.stopAnimation(nil) 26 | } 27 | 28 | self.lastConnectionView?.stringValue = ConfigEditViewController.stringForState(self.availabilityCheckState) 29 | } 30 | } 31 | 32 | override var editing: Bool { 33 | didSet { 34 | self.trashButton.isEnabled = self.editing 35 | } 36 | } 37 | 38 | @IBOutlet weak var trashButton: NSButton! 39 | @IBOutlet weak var lastConnectionView: NSTextField? 40 | @IBOutlet weak var progressIndicator: NSProgressIndicator? 41 | @IBOutlet weak var serverStatusImageView: NSImageView! 42 | 43 | var valid: Bool = false 44 | 45 | //do not call directly! just override 46 | func checkAvailability(_ statusChanged: @escaping ((_ status: AvailabilityCheckState) -> Void)) { 47 | assertionFailure("Must be overriden by subclasses") 48 | } 49 | 50 | @IBAction final func trashButtonClicked(_ sender: AnyObject) { 51 | self.delete() 52 | } 53 | 54 | func edit() { 55 | self.editing = true 56 | } 57 | 58 | func delete() { 59 | assertionFailure("Must be overriden by subclasses") 60 | } 61 | 62 | final func recheckForAvailability(_ completion: ((_ state: AvailabilityCheckState) -> Void)?) { 63 | self.editingAllowed = false 64 | self.checkAvailability { [weak self] (status) -> Void in 65 | self?.availabilityCheckState = status 66 | if status.isDone() { 67 | completion?(status) 68 | self?.editingAllowed = true 69 | } 70 | } 71 | } 72 | 73 | private static func stringForState(_ state: AvailabilityCheckState) -> String { 74 | switch state { 75 | case .checking: 76 | return "Checking access to server..." 77 | case .failed(let error): 78 | let desc = (error as NSError?)?.localizedDescription ?? "\(String(describing: error))" 79 | return "Failed to access server, error: \n\(desc)" 80 | case .succeeded: 81 | return "Verified access, all is well!" 82 | case .unchecked: 83 | return "" 84 | } 85 | } 86 | 87 | private static func imageNameForStatus(_ status: AvailabilityCheckState) -> String { 88 | switch status { 89 | case .unchecked: 90 | return NSImage.Name.statusNone.rawValue 91 | case .checking: 92 | return NSImage.Name.statusPartiallyAvailable.rawValue 93 | case .succeeded: 94 | return NSImage.Name.statusAvailable.rawValue 95 | case .failed: 96 | return NSImage.Name.statusUnavailable.rawValue 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /Buildasaur/ViewControllers/Base/PresentableViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PresentableViewController.swift 3 | // Buildasaur 4 | // 5 | // Created by Honza Dvorsky on 30/09/2015. 6 | // Copyright © 2015 Honza Dvorsky. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | class PresentableViewController: NSViewController { 12 | 13 | //so that when trying to present the view controller again we 14 | //first look whether it isn't already on screen. 15 | var uniqueIdentifier: String = "" 16 | 17 | //gives VC the ability to safely CREATE vcs without duplicates 18 | var storyboardLoader: StoryboardLoader! 19 | 20 | //gives VCs the ability to present more vcs in unique windows etc 21 | weak var presentingDelegate: PresentableViewControllerDelegate? 22 | } 23 | 24 | protocol PresentableViewControllerDelegate: class { 25 | 26 | func presentViewControllerInUniqueWindow(_ viewController: PresentableViewController) 27 | func closeWindowWithViewController(_ viewController: PresentableViewController) 28 | } 29 | -------------------------------------------------------------------------------- /Buildasaur/ViewControllers/Dashboard/MenuItemManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MenuItemManager.swift 3 | // Buildasaur 4 | // 5 | // Created by Honza Dvorsky on 15/05/15. 6 | // Copyright (c) 2015 Honza Dvorsky. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | import BuildaKit 11 | 12 | class MenuItemManager: NSObject, NSMenuDelegate { 13 | 14 | var syncerManager: SyncerManager! 15 | 16 | private var statusItem: NSStatusItem? 17 | private var firstIndexLastSyncedMenuItem: Int! 18 | 19 | func setupMenuBarItem() { 20 | 21 | let statusBar = NSStatusBar.system 22 | 23 | let statusItem = statusBar.statusItem(withLength: 32) 24 | statusItem.title = "" 25 | statusItem.image = NSImage(named: NSImage.Name(rawValue: "icon")) 26 | statusItem.highlightMode = true 27 | 28 | let menu = NSMenu() 29 | menu.addItem(withTitle: "Open Buildasaur", action: #selector(AppDelegate.showMainWindow), keyEquivalent: "") 30 | menu.addItem(NSMenuItem.separator()) 31 | self.firstIndexLastSyncedMenuItem = menu.numberOfItems 32 | 33 | statusItem.menu = menu 34 | menu.delegate = self 35 | self.statusItem = statusItem 36 | } 37 | 38 | func menuWillOpen(_ menu: NSMenu) { 39 | 40 | //update with last sync/statuses 41 | let syncers = self.syncerManager.syncers 42 | 43 | //remove items for existing syncers 44 | let itemsForSyncers = menu.numberOfItems - self.firstIndexLastSyncedMenuItem 45 | let diffItems = syncers.count - itemsForSyncers 46 | 47 | //this many items need to be created or destroyed 48 | if diffItems > 0 { 49 | for _ in 0.. String in 62 | 63 | let state = SyncerStatePresenter.stringForState(syncer.state, active: syncer.active) 64 | 65 | let repo: String 66 | if let repoName = syncer.project.serviceRepoName() { 67 | repo = repoName 68 | } else { 69 | repo = "???" 70 | } 71 | 72 | let time: String 73 | if let lastSuccess = syncer.lastSuccessfulSyncFinishedDate, syncer.active { 74 | time = "last synced \(lastSuccess.nicelyFormattedRelativeTimeToNow())" 75 | } else { 76 | time = "" 77 | } 78 | 79 | let report = "\(repo) \(state) \(time)" 80 | return report 81 | }) 82 | 83 | //fill into items 84 | for (i, text) in texts.enumerated() { 85 | let idx = self.firstIndexLastSyncedMenuItem + i 86 | let item = menu.item(at: idx) 87 | item?.title = text 88 | } 89 | } 90 | 91 | } 92 | -------------------------------------------------------------------------------- /Buildasaur/ViewControllers/Editables/EmptyBuildTemplateViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EmptyBuildTemplateViewController.swift 3 | // Buildasaur 4 | // 5 | // Created by Honza Dvorsky on 10/6/15. 6 | // Copyright © 2015 Honza Dvorsky. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | import BuildaKit 11 | import BuildaUtils 12 | import XcodeServerSDK 13 | 14 | protocol EmptyBuildTemplateViewControllerDelegate: class { 15 | func didSelectBuildTemplate(_ buildTemplate: BuildTemplate) 16 | } 17 | 18 | class EmptyBuildTemplateViewController: EditableViewController { 19 | 20 | //for cases when we're editing an existing syncer - show the 21 | //right preference. 22 | var existingTemplateId: RefType? 23 | 24 | //for requesting just the right build templates 25 | var projectName: String! 26 | 27 | weak var emptyTemplateDelegate: EmptyBuildTemplateViewControllerDelegate? 28 | 29 | @IBOutlet weak var existingBuildTemplatesPopup: NSPopUpButton! 30 | 31 | private var buildTemplates: [BuildTemplate] = [] 32 | private var selectedTemplate: BuildTemplate? = nil { 33 | didSet { 34 | self.nextAllowed = self.selectedTemplate != nil 35 | } 36 | } 37 | 38 | override func viewDidLoad() { 39 | super.viewDidLoad() 40 | 41 | precondition(self.projectName != nil) 42 | 43 | self.setupDataSource() 44 | self.setupPopupAction() 45 | } 46 | 47 | override func viewDidAppear() { 48 | super.viewDidAppear() 49 | 50 | //select if existing template is being edited 51 | //TODO: also the actual index in the popup must be selected! 52 | let index: Int 53 | if let id = self.selectedTemplate?.id { 54 | let ids = self.buildTemplates.map { $0.id } 55 | index = ids.index(of: id) ?? 0 56 | } else if let configId = self.existingTemplateId { 57 | let ids = self.buildTemplates.map { $0.id } 58 | index = ids.index(of: configId) ?? 0 59 | } else { 60 | index = 0 61 | } 62 | self.selectItemAtIndex(index) 63 | self.existingBuildTemplatesPopup.selectItem(at: index) 64 | self.nextAllowed = self.selectedTemplate != nil 65 | } 66 | 67 | private var addNewString: String { 68 | return "Add new build template..." 69 | } 70 | 71 | func newTemplate() -> BuildTemplate { 72 | return BuildTemplate(projectName: self.projectName) 73 | } 74 | 75 | override func shouldGoNext() -> Bool { 76 | self.didSelectBuildTemplate(self.selectedTemplate!) 77 | return super.shouldGoNext() 78 | } 79 | 80 | private func selectItemAtIndex(_ index: Int) { 81 | 82 | let templates = self.buildTemplates 83 | 84 | // last item is "add new" 85 | let template = (index == templates.count) ? self.newTemplate() : templates[index] 86 | self.selectedTemplate = template 87 | } 88 | 89 | private func setupPopupAction() { 90 | self.existingBuildTemplatesPopup.onClick = { [weak self] _ in 91 | guard let sself = self else { return } 92 | let index = sself.existingBuildTemplatesPopup.indexOfSelectedItem 93 | sself.selectItemAtIndex(index) 94 | } 95 | } 96 | 97 | private func setupDataSource() { 98 | let update = { [weak self] in 99 | guard let sself = self else { return } 100 | 101 | sself.buildTemplates = sself.storageManager.buildTemplatesForProjectName(projectName: sself.projectName).sorted { $0.name < $1.name } 102 | let popup = sself.existingBuildTemplatesPopup 103 | popup?.removeAllItems() 104 | var configDisplayNames = sself.buildTemplates.map { template -> String in 105 | let project = template.projectName ?? "" 106 | return "\(template.name) (\(project))" 107 | } 108 | configDisplayNames.append(sself.addNewString) 109 | popup?.addItems(withTitles: configDisplayNames) 110 | } 111 | self.storageManager.onUpdateBuildTemplates = update 112 | update() 113 | } 114 | 115 | private func didSelectBuildTemplate(_ template: BuildTemplate) { 116 | Log.verbose("Selected \(template.name)") 117 | self.emptyTemplateDelegate?.didSelectBuildTemplate(template) 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /Buildasaur/ViewControllers/Editables/EmptyXcodeServerViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EmptyXcodeServerViewController.swift 3 | // Buildasaur 4 | // 5 | // Created by Honza Dvorsky on 10/3/15. 6 | // Copyright © 2015 Honza Dvorsky. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import BuildaKit 11 | import BuildaUtils 12 | import XcodeServerSDK 13 | 14 | protocol EmptyXcodeServerViewControllerDelegate: class { 15 | func didSelectXcodeServerConfig(_ config: XcodeServerConfig) 16 | } 17 | 18 | class EmptyXcodeServerViewController: EditableViewController { 19 | 20 | //for cases when we're editing an existing syncer - show the 21 | //right preference. 22 | var existingConfigId: RefType? 23 | 24 | weak var emptyServerDelegate: EmptyXcodeServerViewControllerDelegate? 25 | 26 | @IBOutlet weak var existingXcodeServersPopup: NSPopUpButton! 27 | 28 | private var xcodeServerConfigs: [XcodeServerConfig] = [] 29 | private var selectedConfig: XcodeServerConfig? { 30 | didSet { 31 | self.nextAllowed = self.selectedConfig != nil 32 | } 33 | } 34 | 35 | override func viewDidLoad() { 36 | super.viewDidLoad() 37 | 38 | self.setupDataSource() 39 | self.setupPopupAction() 40 | } 41 | 42 | override func viewWillAppear() { 43 | super.viewWillAppear() 44 | self.nextTitle = "Next" 45 | self.previousAllowed = self.existingConfigId != nil 46 | } 47 | 48 | override func viewDidAppear() { 49 | super.viewDidAppear() 50 | 51 | //select if existing config is being edited 52 | let index: Int 53 | if let id = self.selectedConfig?.id { 54 | let ids = self.xcodeServerConfigs.map { $0.id } 55 | index = ids.index(of: id) ?? 0 56 | } else if let configId = self.existingConfigId { 57 | let ids = self.xcodeServerConfigs.map { $0.id } 58 | index = ids.index(of: configId) ?? 0 59 | } else { 60 | index = 0 61 | } 62 | self.selectItemAtIndex(index) 63 | self.existingXcodeServersPopup.selectItem(at: index) 64 | self.nextAllowed = self.selectedConfig != nil 65 | } 66 | 67 | private var addNewString: String { 68 | return "Add new Xcode Server..." 69 | } 70 | 71 | private func newConfig() -> XcodeServerConfig { 72 | return XcodeServerConfig() 73 | } 74 | 75 | override func shouldGoNext() -> Bool { 76 | self.didSelectXcodeServer(self.selectedConfig!) 77 | return super.shouldGoNext() 78 | } 79 | 80 | private func selectItemAtIndex(_ index: Int) { 81 | let configs = self.xcodeServerConfigs 82 | // last item is "add new" 83 | let config = (index == configs.count) ? self.newConfig() : configs[index] 84 | self.selectedConfig = config 85 | } 86 | 87 | private func setupPopupAction() { 88 | self.existingXcodeServersPopup.onClick = { [weak self] _ in 89 | if let index = self?.existingXcodeServersPopup.indexOfSelectedItem { 90 | self?.selectItemAtIndex(index) 91 | } 92 | } 93 | } 94 | 95 | private func setupDataSource() { 96 | let update = { [weak self] in 97 | guard let sself = self else { return } 98 | 99 | sself.xcodeServerConfigs = sself.storageManager.serverConfigs.values.sorted { 100 | $0.host < $1.host 101 | } 102 | 103 | let popup = sself.existingXcodeServersPopup 104 | popup?.removeAllItems() 105 | var configDisplayNames = sself.xcodeServerConfigs.map { "\($0.host) (\($0.user ?? String()))" } 106 | configDisplayNames.append(sself.addNewString) 107 | popup?.addItems(withTitles: configDisplayNames) 108 | } 109 | self.storageManager.onUpdateServerConfigs = update 110 | update() 111 | } 112 | 113 | private func didSelectXcodeServer(_ config: XcodeServerConfig) { 114 | Log.verbose("Selected \(config.host)") 115 | self.emptyServerDelegate?.didSelectXcodeServerConfig(config) 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /Buildasaur/ViewControllers/Editor/EditableViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EditableViewController.swift 3 | // Buildasaur 4 | // 5 | // Created by Honza Dvorsky on 10/5/15. 6 | // Copyright © 2015 Honza Dvorsky. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | import BuildaUtils 11 | import BuildaKit 12 | 13 | class EditableViewController: NSViewController { 14 | 15 | var storageManager: StorageManager { 16 | return self.syncerManager.storageManager 17 | } 18 | var syncerManager: SyncerManager! 19 | var editingAllowed: Bool = true 20 | var editing: Bool = true 21 | 22 | var nextAllowed: Bool = true { 23 | didSet { 24 | self.onNextAllowedChanged?(self.nextAllowed) 25 | } 26 | } 27 | var previousAllowed: Bool = true { 28 | didSet { 29 | self.onPreviousAllowedChanged?(self.previousAllowed) 30 | } 31 | } 32 | var cancelAllowed: Bool = true 33 | 34 | var nextTitle: String = "Next" { 35 | didSet { 36 | self.onNextTitleChanged?(self.nextTitle) 37 | } 38 | } 39 | 40 | var onNextAllowedChanged: ((Bool) -> Void)? 41 | var onPreviousAllowedChanged: ((Bool) -> Void)? 42 | var onCancelAllowedChanged: ((Bool) -> Void)? 43 | var onNextTitleChanged: ((String) -> Void)? 44 | 45 | var onWantsNext: ((Bool) -> Void)? 46 | var onWantsPrevious: ((Bool) -> Void)? 47 | 48 | //call from inside of controllers, e.g. 49 | //when shouldGoNext starts validating and it succeeds some time later, 50 | //call goNext to finish going next. otherwise don't call 51 | //and force user to fix the problem. 52 | 53 | final func goNext(animated: Bool = false) { 54 | self.onWantsNext?(animated) 55 | } 56 | 57 | final func goPrevious() { 58 | self.onWantsPrevious?(false) 59 | } 60 | 61 | //for overriding 62 | 63 | func shouldGoNext() -> Bool { 64 | return true 65 | } 66 | 67 | func shouldGoPrevious() -> Bool { 68 | return true 69 | } 70 | 71 | func shouldCancel() -> Bool { 72 | return true 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /Buildasaur/ViewControllers/Editor/EditorContext.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EditorContext.swift 3 | // Buildasaur 4 | // 5 | // Created by Honza Dvorsky on 10/5/15. 6 | // Copyright © 2015 Honza Dvorsky. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import BuildaKit 11 | 12 | struct EditorContext { 13 | var configTriplet: EditableConfigTriplet! 14 | var syncerManager: SyncerManager! 15 | weak var editeeDelegate: EditeeDelegate? 16 | } 17 | -------------------------------------------------------------------------------- /Buildasaur/ViewControllers/Editor/EditorState.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EditorState.swift 3 | // Buildasaur 4 | // 5 | // Created by Honza Dvorsky on 10/5/15. 6 | // Copyright © 2015 Honza Dvorsky. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | enum EditorState: Int { 12 | 13 | case initial 14 | 15 | case noServer 16 | case editingServer 17 | 18 | case noProject 19 | case editingProject 20 | 21 | case noBuildTemplate 22 | case editingBuildTemplate 23 | 24 | case syncer 25 | 26 | case final 27 | 28 | func next() -> EditorState? { 29 | return self + 1 30 | } 31 | 32 | func previous() -> EditorState? { 33 | return self + (-1) 34 | } 35 | } 36 | 37 | extension EditorState: Comparable { } 38 | 39 | func < (lhs: EditorState, rhs: EditorState) -> Bool { 40 | return lhs.rawValue < rhs.rawValue 41 | } 42 | 43 | func + (lhs: EditorState, rhs: Int) -> EditorState? { 44 | return EditorState(rawValue: lhs.rawValue + rhs) 45 | } 46 | -------------------------------------------------------------------------------- /Buildasaur/ViewControllers/Editor/MainEditor_EditeeDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MainEditor_EditeeDelegate.swift 3 | // Buildasaur 4 | // 5 | // Created by Honza Dvorsky on 10/5/15. 6 | // Copyright © 2015 Honza Dvorsky. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import BuildaKit 11 | import XcodeServerSDK 12 | 13 | //here conform to all the delegates of the view controllers and 14 | //figure out what the required actions are. 15 | 16 | extension MainEditorViewController: EditeeDelegate { } 17 | 18 | extension MainEditorViewController: EmptyXcodeServerViewControllerDelegate { 19 | 20 | func didSelectXcodeServerConfig(_ config: XcodeServerConfig) { 21 | self.context.configTriplet.server = config 22 | } 23 | } 24 | 25 | extension MainEditorViewController: XcodeServerViewControllerDelegate { 26 | 27 | func didCancelEditingOfXcodeServerConfig(_ config: XcodeServerConfig) { 28 | self.context.configTriplet.server = nil 29 | self.previous(animated: false) 30 | } 31 | 32 | func didSaveXcodeServerConfig(_ config: XcodeServerConfig) { 33 | self.context.configTriplet.server = config 34 | } 35 | } 36 | 37 | extension MainEditorViewController: EmptyProjectViewControllerDelegate { 38 | 39 | func didSelectProjectConfig(_ config: ProjectConfig) { 40 | self.context.configTriplet.project = config 41 | } 42 | } 43 | 44 | extension MainEditorViewController: ProjectViewControllerDelegate { 45 | 46 | func didCancelEditingOfProjectConfig(_ config: ProjectConfig) { 47 | self.context.configTriplet.project = nil 48 | self.previous(animated: false) 49 | } 50 | 51 | func didSaveProjectConfig(_ config: ProjectConfig) { 52 | self.context.configTriplet.project = config 53 | } 54 | } 55 | 56 | extension MainEditorViewController: EmptyBuildTemplateViewControllerDelegate { 57 | 58 | func didSelectBuildTemplate(_ buildTemplate: BuildTemplate) { 59 | self.context.configTriplet.buildTemplate = buildTemplate 60 | } 61 | } 62 | 63 | extension MainEditorViewController: BuildTemplateViewControllerDelegate { 64 | 65 | func didCancelEditingOfBuildTemplate(_ template: BuildTemplate) { 66 | self.context.configTriplet.buildTemplate = nil 67 | self.previous(animated: false) 68 | } 69 | 70 | func didSaveBuildTemplate(_ template: BuildTemplate) { 71 | self.context.configTriplet.buildTemplate = template 72 | } 73 | } 74 | 75 | extension MainEditorViewController: SyncerViewControllerDelegate { 76 | 77 | func didCancelEditingOfSyncerConfig(_ config: SyncerConfig) { 78 | self._cancel() 79 | } 80 | 81 | func didSaveSyncerConfig(_ config: SyncerConfig) { 82 | self.context.configTriplet.syncer = config 83 | } 84 | 85 | func didRequestEditing() { 86 | self.state = (.noServer, true) 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /Buildasaur/ViewControllers/Editor/MainEditor_ViewManipulation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MainEditor_ViewManipulation.swift 3 | // Buildasaur 4 | // 5 | // Created by Honza Dvorsky on 10/5/15. 6 | // Copyright © 2015 Honza Dvorsky. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | extension MainEditorViewController { 12 | 13 | //view controller manipulation 14 | 15 | private func rebindContentViewController() { 16 | let content = self._contentViewController! 17 | 18 | content.onNextAllowedChanged = { [weak self] nextAllowed in 19 | self?.nextButton.isEnabled = nextAllowed 20 | } 21 | content.onPreviousAllowedChanged = { [weak self] previousAllowed in 22 | self?.previousButton.isEnabled = previousAllowed 23 | } 24 | content.onCancelAllowedChanged = { [weak self] cancelAllowed in 25 | self?.cancelButton.isEnabled = cancelAllowed 26 | } 27 | content.onNextTitleChanged = { [weak self] nextTitle in 28 | self?.nextButton.title = nextTitle 29 | } 30 | content.onWantsNext = { [weak self] animated in 31 | self?._next(animated: animated) 32 | } 33 | content.onWantsPrevious = { [weak self] animated in 34 | self?._previous(animated: animated) 35 | } 36 | } 37 | 38 | private func remove(_ viewController: NSViewController?) { 39 | guard let vc = viewController else { return } 40 | vc.view.removeFromSuperview() 41 | vc.removeFromParentViewController() 42 | } 43 | 44 | private func add(_ viewController: EditableViewController) { 45 | self.addChildViewController(viewController) 46 | let view = viewController.view 47 | 48 | //also match backgrounds? 49 | view.wantsLayer = true 50 | view.layer!.backgroundColor = self.containerView.layer!.backgroundColor 51 | 52 | //setup 53 | self._contentViewController = viewController 54 | self.rebindContentViewController() 55 | 56 | self.containerView.addSubview(view) 57 | } 58 | 59 | func setContentViewController(_ viewController: EditableViewController, animated: Bool) { 60 | //1. remove the old view 61 | self.remove(self._contentViewController) 62 | 63 | //2. add the new view on top of the old one 64 | self.add(viewController) 65 | 66 | //if no animation, complete immediately 67 | if !animated { 68 | return 69 | } 70 | 71 | //animation, yay! 72 | 73 | let newView = viewController.view 74 | 75 | //3. offset the new view to the right 76 | var startingFrame = newView.frame 77 | let originalFrame = startingFrame 78 | startingFrame.origin.x += startingFrame.size.width 79 | newView.frame = startingFrame 80 | 81 | //4. start an animation from right to the center 82 | NSAnimationContext.runAnimationGroup({ (context: NSAnimationContext) -> Void in 83 | context.duration = 0.3 84 | newView.animator().frame = originalFrame 85 | }) 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /Buildasaur/Views/SeparatorView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SeparatorView.swift 3 | // Buildasaur 4 | // 5 | // Created by Honza Dvorsky on 07/03/2015. 6 | // Copyright (c) 2015 Honza Dvorsky. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import AppKit 11 | 12 | class SeparatorView: NSView { 13 | 14 | required init?(coder: NSCoder) { 15 | 16 | super.init(coder: coder) 17 | 18 | self.wantsLayer = true 19 | self.layer!.backgroundColor = NSColor.lightGray.cgColor 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /Buildasaur/Views/UIUtils.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIUtils.swift 3 | // Buildasaur 4 | // 5 | // Created by Honza Dvorsky on 07/03/2015. 6 | // Copyright (c) 2015 Honza Dvorsky. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import AppKit 11 | import BuildaUtils 12 | 13 | open class UIUtils { 14 | 15 | open class func showAlertWithError(_ error: Error) { 16 | 17 | let alert = self.createErrorAlert(error) 18 | self.presentAlert(alert, completion: { (_) -> Void in 19 | // 20 | }) 21 | } 22 | 23 | open class func showAlertAskingConfirmation(_ text: String, dangerButton: String, completion: @escaping (_ confirmed: Bool) -> Void) { 24 | 25 | let buttons = ["Cancel", dangerButton] 26 | self.showAlertWithButtons(text, buttons: buttons) { (tappedButton) -> Void in 27 | completion(dangerButton == tappedButton) 28 | } 29 | } 30 | 31 | open class func showAlertAskingForRemoval(_ text: String, completion: @escaping (_ remove: Bool) -> Void) { 32 | self.showAlertAskingConfirmation(text, dangerButton: "Remove", completion: completion) 33 | } 34 | 35 | open class func showAlertWithButtons(_ text: String, buttons: [String], style: NSAlert.Style? = nil, completion: @escaping (_ tappedButton: String) -> Void) { 36 | 37 | let alert = self.createAlert(text, style: style) 38 | 39 | buttons.forEach { alert.addButton(withTitle: $0) } 40 | 41 | self.presentAlert(alert, completion: { (resp) -> Void in 42 | 43 | //some magic where indices are starting at 1000... so subtract 1000 to get the array index of tapped button 44 | let idx: Int = resp.rawValue - NSApplication.ModalResponse.alertFirstButtonReturn.rawValue 45 | let buttonText = buttons[idx] 46 | completion(buttonText) 47 | }) 48 | } 49 | 50 | open class func showAlertWithText(_ text: String, style: NSAlert.Style? = nil, completion: ((NSApplication.ModalResponse) -> Void)? = nil) { 51 | 52 | let alert = self.createAlert(text, style: style) 53 | self.presentAlert(alert, completion: completion) 54 | } 55 | 56 | private class func createErrorAlert(_ error: Error) -> NSAlert { 57 | return NSAlert(error: error as NSError) 58 | } 59 | 60 | private class func createAlert(_ text: String, style: NSAlert.Style?) -> NSAlert { 61 | 62 | let alert = NSAlert() 63 | 64 | alert.alertStyle = style ?? .informational 65 | alert.messageText = text 66 | 67 | return alert 68 | } 69 | 70 | private class func presentAlert(_ alert: NSAlert, completion: ((NSApplication.ModalResponse) -> Void)?) { 71 | 72 | if NSApp.windows.first != nil { 73 | let resp = alert.runModal() 74 | completion?(resp) 75 | // alert.beginSheetModalForWindow(window, completionHandler: completion) 76 | } else { 77 | //no window to present in, at least print 78 | Log.info("Alert: \(alert.messageText)") 79 | } 80 | } 81 | } 82 | 83 | extension NSPopUpButton { 84 | 85 | public func replaceItems(_ newItems: [String]) { 86 | self.removeAllItems() 87 | self.addItems(withTitles: newItems) 88 | } 89 | } 90 | 91 | extension NSButton { 92 | 93 | public var on: Bool { 94 | get { return self.state == .on } 95 | set { self.state = newValue ? .on : .off } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /Buildasaur/launch_item.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 10 | 11 | 12 | Label 13 | com.honzadvorsky.Buildasaur 14 | ProgramArguments 15 | 16 | LAUNCH_PATH_PLACEHOLDER 17 | 18 | RunAtLoad 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, gender identity and expression, level of experience, 9 | nationality, personal appearance, race, religion, or sexual identity and 10 | orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at czechboy0@gmail.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at [http://contributor-covenant.org/version/1/4][version] 72 | 73 | [homepage]: http://contributor-covenant.org 74 | [version]: http://contributor-covenant.org/version/1/4/ 75 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Honza Dvorsky 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /Meta/bitbucket.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s-faychatelard/Buildasaur/85ec3db576f5995e69d0974b6f650be6d0c912ee/Meta/bitbucket.png -------------------------------------------------------------------------------- /Meta/builda_screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s-faychatelard/Buildasaur/85ec3db576f5995e69d0974b6f650be6d0c912ee/Meta/builda_screenshot.png -------------------------------------------------------------------------------- /Meta/comment.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s-faychatelard/Buildasaur/85ec3db576f5995e69d0974b6f650be6d0c912ee/Meta/comment.png -------------------------------------------------------------------------------- /Meta/github.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s-faychatelard/Buildasaur/85ec3db576f5995e69d0974b6f650be6d0c912ee/Meta/github.png -------------------------------------------------------------------------------- /Meta/menu_bar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s-faychatelard/Buildasaur/85ec3db576f5995e69d0974b6f650be6d0c912ee/Meta/menu_bar.png -------------------------------------------------------------------------------- /Podfile: -------------------------------------------------------------------------------- 1 | 2 | plugin 'cocoapods-keys', { 3 | :keys => [ 4 | "GitHubAPIClientId", 5 | "GitHubAPIClientSecret", 6 | "BitBucketAPIClientId", 7 | "BitBucketAPIClientSecret" 8 | ]} 9 | 10 | source 'https://github.com/CocoaPods/Specs.git' 11 | source 'https://github.com/s-faychatelard/Buildasaur-podspecs.git' 12 | 13 | project 'Buildasaur', 'Testing' => :debug 14 | 15 | platform :osx, '10.11' 16 | use_frameworks! 17 | inhibit_all_warnings! 18 | 19 | def pods_for_errbody 20 | pod 'BuildaUtils', '~> 0.4.2' 21 | end 22 | 23 | def also_xcode_pods 24 | pods_for_errbody 25 | pod 'XcodeServerSDK', '~> 0.7.4' 26 | pod 'ekgclient', '~> 0.3.3' 27 | end 28 | 29 | def buildasaur_app_pods 30 | also_xcode_pods 31 | pod 'Ji', '~> 2.0.1' 32 | pod 'CryptoSwift', '~> 0.7.2' 33 | pod 'Sparkle' 34 | pod 'KeychainAccess', '~> 3.1.0' 35 | end 36 | 37 | def test_pods 38 | pod 'Nimble', '~> 7.0.2' 39 | pod 'DVR', '~> 1.1.0' 40 | end 41 | 42 | target 'Buildasaur' do 43 | buildasaur_app_pods 44 | pod 'Crashlytics' 45 | pod 'OAuthSwift' 46 | pod 'SwiftLint' 47 | end 48 | 49 | target 'BuildaKit' do 50 | buildasaur_app_pods 51 | end 52 | 53 | target 'BuildaKitTests' do 54 | buildasaur_app_pods 55 | test_pods 56 | end 57 | 58 | target 'BuildaGitServer' do 59 | pods_for_errbody 60 | end 61 | 62 | target 'BuildaGitServerTests' do 63 | pods_for_errbody 64 | test_pods 65 | end 66 | 67 | target 'BuildaHeartbeatKit' do 68 | also_xcode_pods 69 | end 70 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - Alamofire (4.5.1) 3 | - BuildaUtils (0.4.2): 4 | - SwiftSafe (~> 2.0.1) 5 | - Crashlytics (3.9.3): 6 | - Fabric (~> 1.7.2) 7 | - CryptoSwift (0.7.2) 8 | - DVR (1.1.0) 9 | - ekgclient (0.3.3): 10 | - Alamofire (~> 4.5.1) 11 | - Fabric (1.7.2) 12 | - Ji (2.0.1): 13 | - Ji/Ji (= 2.0.1) 14 | - Ji/Ji (2.0.1): 15 | - Ji/Ji-libxml 16 | - Ji/Ji-libxml (2.0.1) 17 | - KeychainAccess (3.1.0) 18 | - Keys (1.0.0) 19 | - Nimble (7.0.2) 20 | - OAuthSwift (1.2.0) 21 | - Sparkle (1.18.1) 22 | - SwiftLint (0.24.0) 23 | - SwiftSafe (2.0.1) 24 | - XcodeServerSDK (0.7.4): 25 | - BuildaUtils (~> 0.4.1) 26 | 27 | DEPENDENCIES: 28 | - BuildaUtils (~> 0.4.2) 29 | - Crashlytics 30 | - CryptoSwift (~> 0.7.2) 31 | - DVR (~> 1.1.0) 32 | - ekgclient (~> 0.3.3) 33 | - Ji (~> 2.0.1) 34 | - KeychainAccess (~> 3.1.0) 35 | - Keys (from `Pods/CocoaPodsKeys`) 36 | - Nimble (~> 7.0.2) 37 | - OAuthSwift 38 | - Sparkle 39 | - SwiftLint 40 | - XcodeServerSDK (~> 0.7.4) 41 | 42 | EXTERNAL SOURCES: 43 | Keys: 44 | :path: Pods/CocoaPodsKeys 45 | 46 | SPEC CHECKSUMS: 47 | Alamofire: 2d95912bf4c34f164fdfc335872e8c312acaea4a 48 | BuildaUtils: 0de3c12b40ece8f24277ec66c9dd2e3a3c04f19e 49 | Crashlytics: dbb07d01876c171c5ccbdf7826410380189e452c 50 | CryptoSwift: cd2158be2bf639aabc63ec804707c7137c90157c 51 | DVR: f785f86273f7a3a94335acf44611ac2cbf5920f9 52 | ekgclient: f55ba63142c3545e22f98511127812c46a67828b 53 | Fabric: 9cd6a848efcf1b8b07497e0b6a2e7d336353ba15 54 | Ji: 6bb602a28dd08b804158f64990e6051ed5de0110 55 | KeychainAccess: 94c5540b32eabf7bc32bfb976a268e8ea05fd6da 56 | Keys: 9c35bf00f612ee1d48556f4a4b9b4551e224e90f 57 | Nimble: bfe1f814edabba69ff145cb1283e04ed636a67f2 58 | OAuthSwift: 7fd6855b8e4d58eb5a30d156ea9bed7a8aecd1ca 59 | Sparkle: 06ea33170007c5937ee54da481b4481af98fac79 60 | SwiftLint: a014c92b4664e8b13f380f8640a51bb1733778ba 61 | SwiftSafe: bd14d81b4e458790796e089af07ae740147dc6f3 62 | XcodeServerSDK: 30093232b46f5d18b797f7579ec298bee5686d68 63 | 64 | PODFILE CHECKSUM: 74f826ab5eec2793be7e0f272a12643285e9a784 65 | 66 | COCOAPODS: 1.3.1 67 | -------------------------------------------------------------------------------- /fastlane/README.md: -------------------------------------------------------------------------------- 1 | fastlane documentation 2 | ================ 3 | # Installation 4 | 5 | Make sure you have the latest version of the Xcode command line tools installed: 6 | 7 | ``` 8 | xcode-select --install 9 | ``` 10 | 11 | ## Choose your installation method: 12 | 13 | 14 | 15 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 |
Homebrew 16 | Installer Script 17 | Rubygems 18 |
macOSmacOSmacOS or Linux with Ruby 2.0.0 or above
brew cask install fastlaneDownload the zip file. Then double click on the install script (or run it in a terminal window).sudo gem install fastlane -NV
30 | 31 | # Available Actions 32 | ### prebuild 33 | ``` 34 | fastlane prebuild 35 | ``` 36 | 37 | ### install_cocoapods_if_needed 38 | ``` 39 | fastlane install_cocoapods_if_needed 40 | ``` 41 | 42 | ### test 43 | ``` 44 | fastlane test 45 | ``` 46 | 47 | ### release 48 | ``` 49 | fastlane release 50 | ``` 51 | 52 | ### release_app 53 | ``` 54 | fastlane release_app 55 | ``` 56 | 57 | ### build 58 | ``` 59 | fastlane build 60 | ``` 61 | 62 | 63 | ---- 64 | 65 | This README.md is auto-generated and will be re-generated every time [fastlane](https://fastlane.tools) is run. 66 | More information about fastlane can be found on [fastlane.tools](https://fastlane.tools). 67 | The documentation of fastlane can be found on [docs.fastlane.tools](https://docs.fastlane.tools). 68 | -------------------------------------------------------------------------------- /fastlane/actions/render_github_markdown.rb: -------------------------------------------------------------------------------- 1 | module Fastlane 2 | module Actions 3 | module SharedValues 4 | end 5 | 6 | class RenderGithubMarkdownAction < Action 7 | def self.run(params) 8 | 9 | contents = params[:markdown_contents] || File.read(params[:markdown_file]) 10 | raise "You must pass either the markdown contents or a file path" unless contents 11 | 12 | require 'json' 13 | body = { 14 | text: contents, 15 | mode: "gfm", 16 | context: params[:context_repository] 17 | }.to_json 18 | 19 | require 'base64' 20 | headers = { 21 | 'User-Agent' => 'fastlane-render_github_markdown', 22 | 'Authorization' => "Basic #{Base64.strict_encode64(params[:api_token])}" 23 | } 24 | 25 | response = Excon.post("https://api.github.com/markdown", headers: headers, body: body) 26 | 27 | raise response[:headers].to_s unless response[:status] == 200 28 | html_markdown = response.body 29 | 30 | ENV['RENDER_GITHUB_MARKDOWN_HTML'] = html_markdown 31 | return html_markdown 32 | end 33 | 34 | ##################################################### 35 | # @!group Documentation 36 | ##################################################### 37 | 38 | def self.description 39 | "Uses the GitHub API to render your GitHub-flavored Markdown as HTML" 40 | end 41 | 42 | def self.available_options 43 | [ 44 | FastlaneCore::ConfigItem.new(key: :markdown_file, 45 | env_name: "FL_RENDER_GITHUB_MARKDOWN_FILE", 46 | description: "The path to your markdown file", 47 | optional: true, 48 | verify_block: proc do |value| 49 | raise "File doesn't exist '#{value}'".red unless File.exists?(value) 50 | end), 51 | FastlaneCore::ConfigItem.new(key: :markdown_contents, 52 | env_name: "FL_RENDER_GITHUB_MARKDOWN_CONTENTS", 53 | optional: true, 54 | description: "The markdown contents"), 55 | FastlaneCore::ConfigItem.new(key: :context_repository, 56 | env_name: "FL_RENDER_GITHUB_MARKDOWN_CONTEXT_REPOSITORY", 57 | description: "The path to your repo, e.g. 'fastlane/fastlane'", 58 | verify_block: proc do |value| 59 | raise "Please only pass the path, e.g. 'fastlane/fastlane'".red if value.include? "github.com" 60 | raise "Please only pass the path, e.g. 'fastlane/fastlane'".red if value.split('/').count != 2 61 | end), 62 | FastlaneCore::ConfigItem.new(key: :api_token, 63 | env_name: "FL_RENDER_GITHUB_MARKDOWN_API_TOKEN", 64 | description: "Personal API Token for GitHub - generate one at https://github.com/settings/tokens", 65 | is_string: true, 66 | optional: false), 67 | ] 68 | end 69 | 70 | def self.output 71 | [ 72 | ['RENDER_GITHUB_MARKDOWN_HTML', 'Rendered HTML'] 73 | ] 74 | end 75 | 76 | def self.return_value 77 | "Returns the GFM Markdown contents rendered as HTML" 78 | end 79 | 80 | def self.authors 81 | ["czechboy0"] 82 | end 83 | 84 | def self.is_supported?(platform) 85 | true 86 | end 87 | end 88 | end 89 | end 90 | -------------------------------------------------------------------------------- /sparkle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Buildasaur Changelog 5 | https://raw.githubusercontent.com/buildasaurs/Buildasaur/master/sparkle.xml 6 | Most recent changes with links to updates. 7 | en 8 | 9 | v1.0.0-b1 - First Beta of Iris 10 | 10.11 11 | https://buildasaurs.github.io/Buildasaur/Sparkle_Release_Notes/v1.0.0-b1.html 12 | Wed, 03 Feb 2016 17:08:34 +0000 13 | 14 | 15 | 16 | v1.0.0-b2 - Second Iris Beta, bugfixes, feature complete 17 | 10.11 18 | https://buildasaurs.github.io/Buildasaur/Sparkle_Release_Notes/v1.0.0-b2.html 19 | Thu, 04 Feb 2016 18:21:38 +0000 20 | 21 | 22 | 23 | v1.0.0-b3 - Bugfixes and minor improvements 24 | 10.11 25 | https://buildasaurs.github.io/Buildasaur/Sparkle_Release_Notes/v1.0.0-b3.html 26 | Tue, 29 Mar 2016 09:59:43 +0200 27 | 28 | 29 | 30 | v1.0.1 - Bugfixes, improvements 31 | 10.11 32 | https://buildasaurs.github.io/Buildasaur/Sparkle_Release_Notes/v1.0.1.html 33 | Fri, 17 Jun 2016 16:39:37 +0200 34 | 35 | 36 | 37 | v1.0.2 - Fixes 38 | 10.11 39 | https://buildasaurs.github.io/Buildasaur/Sparkle_Release_Notes/v1.0.2.html 40 | Fri, 12 Aug 2016 12:08:52 +0200 41 | 42 | 43 | 44 | v1.0.3 - Xcode 8 Fixes 45 | 10.11 46 | https://buildasaurs.github.io/Buildasaur/Sparkle_Release_Notes/v1.0.3.html 47 | Fri, 02 Sep 2016 23:36:50 +0200 48 | 49 | 50 | 51 | --------------------------------------------------------------------------------