├── .gitignore ├── LICENSE ├── Mockfile ├── Package.resolved ├── Package.swift ├── README.md ├── Scripts ├── bootstrap_dirs.sh ├── bootstrap_project.sh ├── format_code.sh └── install_local_spm_util.sh ├── Sources ├── AppStoreConnectAPI │ ├── AppStoreConnectAPIClient+Provisioning.swift │ ├── AppStoreConnectAPIClient+Releases.swift │ ├── AppStoreConnectAPIClient.swift │ ├── AppStoreConnectAPIClientProtocol.swift │ ├── AppStoreConnectAPIDTOs.swift │ ├── AppStoreConnectIPAUploader.swift │ ├── AppStoreConnectTokenGenerator.swift │ └── BagbutikServiceProtocol.swift ├── FirebaseAPI │ ├── FirebaseAppDistribution │ │ ├── FirebaseDistributionAPIClient+.swift │ │ ├── FirebaseDistributionAPIClient.swift │ │ ├── FirebaseDistributionAPIClientProtocol.swift │ │ ├── FirebaseDistributionDTOs.swift │ │ └── FirebaseDistributionIPAUploader.swift │ └── GoogleAuth │ │ └── GoogleAuthAPIClient.swift ├── Git │ ├── Git.swift │ ├── GitDiffParser.swift │ ├── GitProtocol.swift │ └── Options │ │ ├── GitBranchOption.swift │ │ ├── GitCreateStashOption.swift │ │ ├── GitPullOption.swift │ │ ├── GitPushOption.swift │ │ └── GitResetOption.swift ├── GitLabAPI │ ├── Deserializer │ │ └── GitLabAPIDeserializer.swift │ ├── GitLabAPIClient │ │ ├── GitLabAPIClient.swift │ │ ├── GitLabAPIClientProtocol.swift │ │ ├── Packages │ │ │ └── GitLabAPIClient+Packages.swift │ │ ├── Read │ │ │ ├── GitLabAPIClient+ApprovalRules.swift │ │ │ ├── GitLabAPIClient+MergeRequests.swift │ │ │ ├── GitLabAPIClient+Pipelines.swift │ │ │ ├── GitLabAPIClient+Repository.swift │ │ │ └── GitLabAPIClient+Users.swift │ │ └── Write │ │ │ ├── GitLabAPIClient+EditMergeRequest.swift │ │ │ ├── GitLabAPIClient+EditPipelines.swift │ │ │ └── GitLabAPIClient+EditRepository.swift │ ├── GitLabAPIComplexClient │ │ ├── GitLabAPIComplexClient.swift │ │ └── GitLabAPIComplexClientProtocol.swift │ ├── GitLabPackagesRegistry.swift │ └── Models │ │ ├── CommitInfo.swift │ │ ├── FileContent.swift │ │ ├── FileDiff.swift │ │ ├── GitLabSpace.swift │ │ ├── Group.swift │ │ ├── Member.swift │ │ ├── MergeRequest.Changes.swift │ │ ├── MergeRequest.Commit.swift │ │ ├── MergeRequest.Note.swift │ │ ├── MergeRequest.State.swift │ │ ├── MergeRequest.User.swift │ │ ├── MergeRequest.swift │ │ ├── MergeRequestApprovalRule.swift │ │ ├── MergeRequestApprovals.swift │ │ ├── Package.swift │ │ ├── Pipeline.swift │ │ ├── RepositoryCompareResult.swift │ │ └── UserActivityEvent.swift ├── Guardian │ ├── Services │ │ ├── GitLabCIEnvironment │ │ │ ├── GitLabCIEnvironmentReader.swift │ │ │ └── GitLabCIEnvironmentVariable.swift │ │ ├── MergeRequestReportFactory │ │ │ ├── MergeRequestReportCaptionProvider.swift │ │ │ ├── MergeRequestReportFactoring.swift │ │ │ └── MergeRequestReportFactory.swift │ │ └── MergeRequestReporter │ │ │ ├── FileMergeRequestReporter.swift │ │ │ ├── GitLabMergeRequestReporter.swift │ │ │ └── MergeRequestReporting.swift │ └── Tasks │ │ └── GuardianBaseTask.swift ├── JiraAPI │ ├── Deserializer │ │ ├── JiraAPIDeserializer.swift │ │ └── JiraAPISerializer.swift │ ├── JiraAPIClient │ │ ├── JiraAPIClient.swift │ │ ├── Read │ │ │ └── JiraAPIClient+Read.swift │ │ └── Write │ │ │ └── JiraAPIClient+Write.swift │ └── Models │ │ ├── AsceticVersion.swift │ │ ├── DetailVersion.swift │ │ ├── Fields.swift │ │ ├── FullVersion.swift │ │ ├── Issue.swift │ │ ├── IssuesStatus.swift │ │ ├── JiraJQL.swift │ │ ├── JiraSearch.swift │ │ ├── Priority.swift │ │ ├── Project.swift │ │ ├── SearchResult.swift │ │ ├── Status.swift │ │ ├── Transition.swift │ │ ├── TransitionsResponse.swift │ │ └── User.swift ├── MattermostAPI │ └── MattermostAPIClient.swift ├── Networking │ ├── Extensions │ │ ├── AnyEncodable.swift │ │ ├── DecodingError+.swift │ │ ├── JSONDecoder+.swift │ │ ├── JSONEncoder+.swift │ │ ├── ProgressUserInfoKey+.swift │ │ └── URLSession+Progress.swift │ ├── Models │ │ ├── DataTaskInfo.swift │ │ ├── HTTPMethod.swift │ │ ├── NetworkingError.swift │ │ ├── NetworkingProgress.swift │ │ ├── NetworkingRequest │ │ │ ├── NetworkingRequest+URLRequest.swift │ │ │ └── NetworkingRequest.swift │ │ ├── NetworkingResponse.swift │ │ ├── ProgressOrResult.swift │ │ ├── ResponseStatus │ │ │ ├── ResponseStatus+ResponseType.swift │ │ │ └── ResponseStatus.swift │ │ └── ResponseType.swift │ └── Services │ │ ├── Dumper │ │ ├── CodableNetworkingRequest.swift │ │ ├── DumpEntry.swift │ │ └── NetworkDumper.swift │ │ ├── Logger │ │ └── NetworkingLogger.swift │ │ ├── NetworkingBasicAuthHeaderFactory.swift │ │ ├── NetworkingClient │ │ ├── NetworkingClient+Builder.swift │ │ ├── NetworkingClient.swift │ │ └── NetworkingClientProtocol.swift │ │ ├── NetworkingProgressLogger.swift │ │ ├── NetworkingRequestBuilder │ │ ├── NetworkingRequestBuilder+Perform.swift │ │ ├── NetworkingRequestBuilder+With.swift │ │ └── NetworkingRequestBuilder.swift │ │ └── Serialization │ │ ├── DeserializerProtocol.swift │ │ ├── JSONDeserializer.swift │ │ ├── JSONSerializer.swift │ │ └── SerializerProtocol.swift ├── Provisioning │ ├── HighLevelServices │ │ ├── Match │ │ │ ├── CertificatesConstants.swift │ │ │ ├── CertsAtomicInstaller.swift │ │ │ ├── CertsGenerator.swift │ │ │ ├── CertsInstaller.swift │ │ │ ├── CertsRepository.swift │ │ │ ├── CertsUpdater.swift │ │ │ └── RemoteCertificateInstaller.swift │ │ └── ProvisioningProfilesService.swift │ └── LowLevelServices │ │ ├── CertificateParser │ │ └── CertificateParser.swift │ │ ├── KeychainService.swift │ │ ├── MacOSSecurity │ │ ├── CodesigningIdentity.swift │ │ └── MacOSSecurity.swift │ │ ├── MobileProvision │ │ ├── MobileProvision.swift │ │ └── MobileProvisionParser.swift │ │ └── OpenSSL │ │ ├── OpenSSLCertificateFormat.swift │ │ ├── OpenSSLCipherCommand.swift │ │ ├── OpenSSLMsgDigest.swift │ │ ├── OpenSSLService.swift │ │ └── OpenSSLX509Options.swift ├── Simulator │ ├── Models │ │ ├── SimCtlDevice.swift │ │ ├── SimCtlRuntime.swift │ │ └── SimulatorError.swift │ ├── RuntimesMiner.swift │ ├── Simulator.swift │ ├── SimulatorCloner.swift │ └── SimulatorProvider.swift ├── Swiftlane │ ├── Commands │ │ ├── AddJiraIssueComment │ │ │ ├── AddJiraIssueCommentCommand.swift │ │ │ └── AddJiraIssueCommentCommandRunner.swift │ │ ├── BuildCommand │ │ │ ├── BuildAppCommand.swift │ │ │ └── BuildAppCommandRunner.swift │ │ ├── BuildNumberCommand │ │ │ ├── BuildNumberCommand.swift │ │ │ ├── BuildNumberSetCommand.swift │ │ │ └── BuildNumberSetCommandRunner.swift │ │ ├── CertsCommands │ │ │ ├── CertsChangePasswordCommand.swift │ │ │ ├── CertsCommand.swift │ │ │ ├── CertsInstallCommand.swift │ │ │ ├── CertsInstallCommandRunner.swift │ │ │ └── CertsUpdateCommand.swift │ │ ├── ChangeJiraIssueLabelsCommand │ │ │ ├── ChangeJiraIssueLabelsCommand.swift │ │ │ └── ChangeJiraIssueLabelsCommandRunner.swift │ │ ├── ChangeVersionCommand │ │ │ ├── ChangeVersionCommand.swift │ │ │ └── ChangeVersionCommandRunner.swift │ │ ├── CheckCommitsCommand │ │ │ ├── CheckCommitsCommand.swift │ │ │ └── CheckCommitsCommandRunner.swift │ │ ├── CheckStopListCommand │ │ │ ├── CheckStopListCommand.swift │ │ │ └── CheckStopListCommandRunner.swift │ │ ├── ExportIPACommand │ │ │ ├── ArchiveAndExportIPACommand.swift │ │ │ └── ArchiveAndExportIPACommandRunner.swift │ │ ├── GuardianAfterBuildCommand │ │ │ ├── GuardianAfterBuildCommand.swift │ │ │ ├── GuardianAfterBuildCommandConfig.swift │ │ │ └── GuardianAfterBuildCommandRunner.swift │ │ ├── GuardianBeforeBuildCommand │ │ │ ├── GuardianBeforeBuildCommand.swift │ │ │ ├── GuardianBeforeBuildCommandConfig.swift │ │ │ └── GuardianBeforeBuildCommandRunner.swift │ │ ├── GuardianCheckAuthorCommand │ │ │ ├── GuardianCheckAuthorCommand.swift │ │ │ └── GuardianCheckAuthorCommandRunner.swift │ │ ├── GuardianInitialNoteCommand │ │ │ ├── GuardianInitialNoteCommand.swift │ │ │ └── GuardianInitialNoteCommandRunner.swift │ │ ├── MeasureBuildTimeCommand │ │ │ ├── MeasureBuildTimeCommand.swift │ │ │ └── MeasureBuildTimeCommandRunner.swift │ │ ├── PatchTestPlanEnvCommand │ │ │ ├── PatchTestPlanEnvCommand.swift │ │ │ └── PatchTestPlanEnvCommandRunner.swift │ │ ├── PingCommand │ │ │ └── PingCommand.swift │ │ ├── ReportUnusedCodeCommand │ │ │ ├── ReportUnusedCodeCommand.swift │ │ │ └── ReportUnusedCodeCommandRunner.swift │ │ ├── RunTestsCommand │ │ │ ├── RunTestsCommand.swift │ │ │ └── RunTestsCommandRunner.swift │ │ ├── SetProvisioningCommand │ │ │ ├── SetProvisioningCommand.swift │ │ │ └── SetProvisioningCommandRunner.swift │ │ ├── SetupAssignesCommand │ │ │ ├── SetupAssigneeCommand.swift │ │ │ └── SetupAssigneeCommandRunner.swift │ │ ├── SetupLabelsCommand │ │ │ ├── SetupLabelsCommand.swift │ │ │ └── SetupLabelsCommandRunner.swift │ │ ├── SetupReviewersCommand │ │ │ ├── SetupReviewersCommand.swift │ │ │ └── SetupReviewersCommandRunner.swift │ │ ├── SwiftlaneCommand.swift │ │ ├── SwiftlaneCommandProtocol │ │ │ └── SwiftlaneCommandProtocol.swift │ │ ├── SwiftlaneRootCommand.swift │ │ ├── UploadGitLabPackageCommand │ │ │ ├── UploadGitLabPackageCommand.swift │ │ │ └── UploadGitLabPackageCommandRunner.swift │ │ ├── UploadToAppStoreCommand │ │ │ ├── UploadToAppStoreCommand.swift │ │ │ └── UploadToAppStoreCommandRunner.swift │ │ └── UploadToFirebaseCommand │ │ │ ├── UploadToFirebaseCommand.swift │ │ │ └── UploadToFirebaseCommandRunner.swift │ ├── CommonRunner │ │ └── CommonRunner.swift │ ├── ConfigModels │ │ ├── GlobalConfig │ │ │ ├── GlobalConfig.swift │ │ │ └── PathsFactory.swift │ │ └── PipelineConfigModel.swift │ ├── Extensions │ │ ├── AnyDecoder+.swift │ │ ├── GitLabAPIClient+.swift │ │ ├── Initable.swift │ │ ├── JiraAPIClient+.swift │ │ ├── MergeRequestReportFactory.swift │ │ ├── Path+.swift │ │ ├── SemVer+.swift │ │ ├── SensitiveData+Argument.swift │ │ └── URL+.swift │ ├── Factories │ │ ├── DependenciesFactory.swift │ │ └── TasksFactory.swift │ ├── RootCommandRunner.swift │ ├── Services │ │ ├── ArtifactsCollector.swift │ │ ├── ChangeLogFactory.swift │ │ ├── ExpiringToDo │ │ │ ├── ExpiringToDoParser.swift │ │ │ ├── ExpiringToDoResponsibilityProvider.swift │ │ │ ├── ExpiringToDoSorter.swift │ │ │ └── ExpiringToDoVerifier.swift │ │ ├── FirebaseCrashlyticsCLIService.swift │ │ ├── MergeRequestInfoProvider │ │ │ ├── MergeRequestInfo.swift │ │ │ └── MergeRequestInfoProvider.swift │ │ ├── MultiScan │ │ │ ├── MultiScan.Config.swift │ │ │ └── MultiScan.swift │ │ ├── Parsers │ │ │ ├── AnyParser.swift │ │ │ ├── JiraParsers │ │ │ │ └── IssueKey │ │ │ │ │ ├── JiraIssueKeyParser.swift │ │ │ │ │ └── JiraIssueKeySearcher.swift │ │ │ ├── Parsing.swift │ │ │ └── SwiftCodeParser.swift │ │ ├── Periphery │ │ │ ├── Models │ │ │ │ ├── PeripheryModels.Accessibility.swift │ │ │ │ ├── PeripheryModels.Annotation.swift │ │ │ │ ├── PeripheryModels.Kind.swift │ │ │ │ ├── PeripheryModels.ScanResult.swift │ │ │ │ └── PeripheryModels.swift │ │ │ ├── PeripheryResultsFormatting.swift │ │ │ ├── PeripheryResultsMarkdownFormatter.swift │ │ │ └── PeripheryService.swift │ │ ├── ProjectVersioning │ │ │ ├── ProjectVersionConverting.swift │ │ │ └── ProjectVersioningService.swift │ │ ├── RangesWelder.swift │ │ ├── Scan │ │ │ ├── Scan.Config.swift │ │ │ └── Scan.swift │ │ ├── SentryCLIService.swift │ │ ├── SharedConfigReader.swift │ │ ├── SimulatorLogsChecker.swift │ │ ├── Slather │ │ │ └── SlatherService.swift │ │ ├── SwiftLint │ │ │ └── SwiftLint.swift │ │ ├── WarningLimits │ │ │ ├── WarningsStorage.Config.swift │ │ │ └── WarningsStorage.swift │ │ ├── XCLogParser │ │ │ ├── XCLogParserIssueFormatter.swift │ │ │ ├── XCLogParserIssuesReport.swift │ │ │ └── XCLogParserService.swift │ │ ├── junit │ │ │ ├── JUnitService.swift │ │ │ └── JUnitTestSuites.swift │ │ └── xccov │ │ │ ├── XCCOVCoverageReport.swift │ │ │ └── XCCOVService.swift │ ├── SharedConfigOptions.swift │ ├── ShellEnvKey.swift │ ├── StdIOWrapper.swift │ ├── Tasks │ │ ├── AddJiraIssueCommentTask │ │ │ └── AddJiraIssueCommentTask.swift │ │ ├── ArchiveAndExportIPATask │ │ │ └── ArchiveAndExportIPATask.swift │ │ ├── BuildAppTask │ │ │ └── BuildAppTask.swift │ │ ├── CertsTasks │ │ │ ├── CertsChangePasswordTask.swift │ │ │ ├── CertsInstallTask.swift │ │ │ └── CertsUpdateTask.swift │ │ ├── ChangeJiraIssueLabelsTask │ │ │ └── ChangeJiraIssueLabelsTask.swift │ │ ├── ChangeVersionTask │ │ │ └── ChangeVersionTask.swift │ │ ├── CheckCommitsTask │ │ │ ├── CheckCommitsTask.swift │ │ │ └── Checkers │ │ │ │ ├── CommitsChecker.swift │ │ │ │ └── CommitsCheckerEnReporter.swift │ │ ├── CheckStopListTask │ │ │ ├── CheckStopListTask.swift │ │ │ └── Checkers │ │ │ │ ├── ContentChecker │ │ │ │ ├── ContentChecker.swift │ │ │ │ └── ContentCheckerEnReporter.swift │ │ │ │ └── FilesChecker │ │ │ │ ├── FilesChecker.swift │ │ │ │ └── FilesCheckerEnReporter.swift │ │ ├── GuardianAfterBuildTask │ │ │ ├── Checkers │ │ │ │ ├── BuildErrorsChecker │ │ │ │ │ ├── BuildErrorsChecker.Config.swift │ │ │ │ │ ├── BuildErrorsChecker.swift │ │ │ │ │ └── BuildErrorsReporter.swift │ │ │ │ ├── BuildWarningsChecker │ │ │ │ │ ├── BuildWarningsChecker.swift │ │ │ │ │ └── BuildWarningsReporter.swift │ │ │ │ ├── ChangesCoverageChecker │ │ │ │ │ ├── ChangesCoverageLimitChecker.Config.swift │ │ │ │ │ ├── ChangesCoverageLimitChecker.swift │ │ │ │ │ ├── ChangesCoverageReporter.swift │ │ │ │ │ └── ChangesCoverageReporting.swift │ │ │ │ ├── TargetCoverageChecker │ │ │ │ │ ├── TargetCoverageCalculator.swift │ │ │ │ │ ├── TargetCoverageReporter.swift │ │ │ │ │ ├── TargetCoverageReporting.swift │ │ │ │ │ ├── TargetsCoverageLimitChecker.Config.swift │ │ │ │ │ ├── TargetsCoverageLimitChecker.swift │ │ │ │ │ └── TargetsCoverageTargetsFilterer.swift │ │ │ │ ├── UnitTestsExitCodeChecker │ │ │ │ │ ├── UnitTestsExitCodeChecker.Config.swift │ │ │ │ │ └── UnitTestsExitCodeChecker.swift │ │ │ │ └── UnitTestsResultsChecker │ │ │ │ │ ├── UnitTestsResultsChecker.Config.swift │ │ │ │ │ ├── UnitTestsResultsChecker.swift │ │ │ │ │ ├── UnitTestsResultsReporter.swift │ │ │ │ │ └── UnitTestsResultsReporting.swift │ │ │ ├── GuardianAfterBuildTask.Config.swift │ │ │ └── GuardianAfterBuildTask.swift │ │ ├── GuardianBeforeBuildTask │ │ │ ├── Checkers │ │ │ │ ├── ExpiringToDoChecker │ │ │ │ │ ├── ExpiringToDoAllowedAuthorsProvider.swift │ │ │ │ │ ├── ExpiringToDoChecker.swift │ │ │ │ │ └── ExpiringToDoReporter.swift │ │ │ │ ├── FilePathChecker │ │ │ │ │ ├── AllowedFilePathChecker.swift │ │ │ │ │ └── FilePathReporter.swift │ │ │ │ ├── MockDeclarationChecker │ │ │ │ │ ├── StubDeclarationChecker.swift │ │ │ │ │ └── StubDeclarationReporter.swift │ │ │ │ └── WarningLimitsChecker │ │ │ │ │ ├── WarningLimitsChecker.swift │ │ │ │ │ ├── WarningLimitsCheckerConfig.swift │ │ │ │ │ ├── WarningLimitsCheckerEnReporter.swift │ │ │ │ │ ├── WarningLimitsCheckerReporting.swift │ │ │ │ │ └── WarningLimitsUntrackedChecker.swift │ │ │ ├── GuardianBeforeBuildTask.Config.swift │ │ │ └── GuardianBeforeBuildTask.swift │ │ ├── GuardianCheckAuthorTask │ │ │ ├── Checkers │ │ │ │ ├── MergeRequestAuthorChecker.swift │ │ │ │ └── MergeRequestAuthorCheckerReporter.swift │ │ │ └── GuardianCheckAuthorTask.swift │ │ ├── GuardianInitialNoteTask │ │ │ └── GuardianInitialNoteTask.swift │ │ ├── MeasureBuildTimeTask │ │ │ └── MeasureBuildTimeTask.swift │ │ ├── PatchTestPlanEnvTask │ │ │ └── PatchTestPlanEnvTask.swift │ │ ├── ReportUnusedCodeTask │ │ │ └── ReportUnusedCodeTask.swift │ │ ├── RunTestsTask │ │ │ ├── RunTestsTask.Config.swift │ │ │ └── RunTestsTask.swift │ │ ├── SetProvisioningTask │ │ │ └── SetProvisioningTask.swift │ │ ├── SetupAssigneeTask │ │ │ └── SetupAssigneeTask.swift │ │ ├── SetupLabelsTask │ │ │ └── SetupLabelsTask.swift │ │ ├── SetupReviewersTask │ │ │ └── SetupReviewersTask.swift │ │ └── UploadGitLabPackageTask │ │ │ └── UploadGitLabPackageTask.swift │ └── version.swift ├── SwiftlaneCLI │ └── main.swift └── Xcodebuild │ ├── ArchiveProcessor │ ├── XCArchiveDSYMsExtractor.swift │ ├── XCArchiveExportOptions.swift │ └── XCArchiveExporter.swift │ ├── Builder │ ├── Builder.Config.swift │ └── Builder.swift │ ├── LogPathFactory.swift │ ├── PlistBuddyService.swift │ ├── TestsRunner │ ├── TestsRunner.Config.swift │ ├── TestsRunner.Errors.swift │ ├── TestsRunner.TestRunResult.swift │ └── TestsRunner.swift │ ├── XCTestProductsServices │ ├── README.md │ ├── XCTest │ │ ├── XCTestParser.swift │ │ └── XCTestService.swift │ ├── XCTestPlan │ │ ├── XCTestPlanFinder.swift │ │ ├── XCTestPlanParser.swift │ │ ├── XCTestPlanPatcher.swift │ │ └── XCTestPlanService.swift │ └── XCTestRun │ │ ├── XCTestRunFinder.swift │ │ └── XCTestRunParser.swift │ ├── XcodeProjectPatcher.swift │ ├── XcodebuildCommandProducer.swift │ └── XcodebuildErrorParser.swift ├── Tests ├── AppStoreConnectAPITests │ └── AppStoreConnectAPITests.swift ├── ArgumentParserExtensionsTests │ └── Stub.swift ├── FirebaseAPITests │ └── FirebaseAPITests.swift ├── GitLabAPITests │ ├── ApprovalRulesTests.swift │ ├── EditMergeRequestsTests.swift │ ├── GitLabAPIComplexClientTests.swift │ ├── MergeRequestsTests.swift │ ├── RepositoryTests.swift │ ├── Stubs.swift │ ├── Stubs │ │ ├── groups │ │ │ ├── 329 │ │ │ │ └── merge_requests │ │ │ │ │ └── 0E8C0E3E-ACE8-4211-9A8B-0B927156FBE1.json │ │ │ └── 81253 │ │ │ │ └── members │ │ │ │ └── 21E7481F-BCAB-4955-B935-6729B10E2AF5.json │ │ ├── projects │ │ │ ├── 333 │ │ │ │ └── repository │ │ │ │ │ └── compare │ │ │ │ │ └── 5A6DD82F-378D-4585-9501-E227372390FD.json │ │ │ └── 75700 │ │ │ │ ├── approval_rules │ │ │ │ └── 7DB4E79E-9121-4019-99E8-415168E63090.json │ │ │ │ └── merge_requests │ │ │ │ ├── 3536 │ │ │ │ ├── 4A0775D4-797E-4B7F-AA25-B8E1534EED76.json │ │ │ │ └── notes │ │ │ │ │ ├── 434124 │ │ │ │ │ ├── ACBFAC5C-7573-4F3E-ACE7-E2271EC4548E.json │ │ │ │ │ └── CB1BD4B1-5386-49E2-8394-4E2DE5468BE3.json │ │ │ │ │ ├── 0BBB909D-90F8-4462-9C02-6CA37727FFF6.json │ │ │ │ │ └── 2F165EF9-F422-48BB-BC9B-F5F49B88C3DF.json │ │ │ │ └── 4539 │ │ │ │ ├── approval_rules │ │ │ │ └── D473F980-8E4E-480D-912E-D68BB4AE3940.json │ │ │ │ ├── approvals │ │ │ │ └── 3F209A32-B6DD-41A1-9B3F-8CDEDC18F1AE.json │ │ │ │ └── changes │ │ │ │ └── 6969BB67-0AE7-420F-B54B-D436044BDDBC.json │ │ └── users │ │ │ └── 631 │ │ │ └── events │ │ │ └── E5289F3E-5BB5-4D88-AC86-CE4534CB9854.json │ └── UsersTests.swift ├── GitTests │ ├── GitDiffParserTests.swift │ └── Stubs │ │ └── diffs │ │ ├── binary.diff │ │ ├── changedOneLine.diff │ │ ├── changedOneLine_and_addedOneLine.diff │ │ ├── chmod.diff │ │ ├── deletedFile.diff │ │ ├── newEmptyFile.diff │ │ ├── newFile2.diff │ │ ├── newFileOneLine.diff │ │ ├── newFileTrickyName.diff │ │ ├── noNewLine.diff │ │ ├── pdf_deleted.diff │ │ ├── renamedFile.diff │ │ └── spacesAndBackslashDiffLine.diff ├── GuardianTests │ ├── GitLabCIEnvironmentReaderTests.swift │ └── MergeRequestReporterTests.swift ├── JiraAPITests │ ├── JiraAPITests.swift │ ├── Stubs.swift │ └── Stubs │ │ ├── issue │ │ └── MDDS-3980 │ │ │ ├── 1639B0A4-354B-45E5-A3B4-9A37D8A69A98.json │ │ │ ├── _1639B0A4-354B-45E5-A3B4-9A37D8A69A98.json │ │ │ └── transitions │ │ │ ├── 31A349BC-A866-446F-AF8C-380548A2544A.json │ │ │ └── _31A349BC-A866-446F-AF8C-380548A2544A.json │ │ ├── project │ │ └── MDDS │ │ │ └── versions │ │ │ ├── 9CE45543-6DF0-4E51-A74F-46407256FAB3.json │ │ │ └── _9CE45543-6DF0-4E51-A74F-46407256FAB3.json │ │ ├── search │ │ ├── 52554490-56DC-426A-A53D-1A521F87A18A.json │ │ ├── E1F091D7-59D7-4C6E-A95B-BC53EF3B1295.json │ │ ├── _52554490-56DC-426A-A53D-1A521F87A18A.json │ │ └── _E1F091D7-59D7-4C6E-A95B-BC53EF3B1295.json │ │ └── version │ │ └── 10561 │ │ ├── D41C3AE6-F7C5-4CD7-9494-E90B335712C3.json │ │ └── _D41C3AE6-F7C5-4CD7-9494-E90B335712C3.json ├── NetworkingTests │ └── NetworkingTests.swift ├── ProvisioningTests │ ├── CertsUpdaterTests.swift │ ├── KeychainServiceTests.swift │ ├── MobileProvisionParserTests.swift │ ├── OpenSSLServiceTests.swift │ └── Stubs │ │ ├── match_AppStore_com.fakecompany.app.mobileprovision │ │ ├── match_AppStore_com.fakecompany.app.plist │ │ ├── rsa_key │ │ ├── rsa_key.pub │ │ ├── rsa_key_csr │ │ └── self-signed-ssl.cer ├── SimulatorTests │ ├── Mocks │ │ └── TestError.swift │ ├── SimulatorProviderTests.swift │ ├── SimulatorTests.swift │ └── Stubs │ │ ├── devices.json │ │ └── runtimes.json ├── SwiftlaneTests │ ├── DITests.swift │ ├── Guardian │ │ └── Checkers │ │ │ ├── BuildErrorsCheckerTests.swift │ │ │ ├── ChangesCoverageLimitCheckerTests.swift │ │ │ ├── ContentCheckerTests.swift │ │ │ ├── StubDeclarationCheckerTests.swift │ │ │ ├── TargetsCoverageCalculatorTests.swift │ │ │ ├── TargetsCoverageLimitCheckerTests.swift │ │ │ ├── UnitTestsExitCodeCheckerTests.swift │ │ │ ├── UnitTestsResultsCheckerTests.swift │ │ │ ├── WarningLimitsCheckerTests.swift │ │ │ └── WarningLimitsUntrackedCheckerTests.swift │ ├── IssueKeyParserTests.swift │ ├── PathsFactoryTests.swift │ ├── Services │ │ ├── ChangelogFactoryTests.swift │ │ ├── ExpiringToDoParserTests.swift │ │ ├── ExpiringToDoSorterTests.swift │ │ ├── ExpiringToDoVerifierTests.swift │ │ ├── RangesWelderTests.swift │ │ ├── Stubs.swift │ │ ├── WarningsStorageTests.swift │ │ └── XCLogParser │ │ │ └── XCLogParserIssueMarkdownFormatterTests.swift │ └── Stubs │ │ ├── SpacesIndentedFile.txt │ │ └── TabsIndentedFile.txt └── XcodebuildTests │ ├── Stubs │ ├── TestPlanWithEnvVariables.json │ ├── TestPlanWithEnvVariablesPatched.json │ ├── TestPlanWithSelectedTests.json │ ├── TestPlanWithSkippedTests.json │ ├── TestPlanWithoutEnvVariables.json │ ├── TestPlanWithoutEnvVariablesPatched.json │ └── xctestruns │ │ ├── NoXCTestPaths.xctestrun │ │ ├── SMUnitTests.xctestrun │ │ ├── SelectedTestsTestPlan.xctestrun │ │ └── SkippedTestsTestPlan.xctestrun │ ├── XCTestPlanFinderTests.swift │ ├── XCTestPlanParserTests.swift │ ├── XCTestPlanPatcherTests.swift │ ├── XCTestPlanServiceTests.swift │ ├── XCTestRunFinderTests.swift │ ├── XCTestRunParserTests.swift │ ├── XCTestServiceTests.swift │ └── XcodebuildErrorParserTests.swift ├── bootstrap └── format_code /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | /*.xcodeproj 5 | xcuserdata/ 6 | DerivedData/ 7 | .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata 8 | 9 | .githooks 10 | *.generated.swift 11 | /etc 12 | 13 | /*.log 14 | -------------------------------------------------------------------------------- /Scripts/bootstrap_dirs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | export SM_UTILS_PATH="./etc/utils" 4 | export SM_UTILS_BIN_PATH="./etc/bin" 5 | -------------------------------------------------------------------------------- /Scripts/bootstrap_project.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -euo pipefail 4 | 5 | source ./Scripts/bootstrap_dirs.sh 6 | 7 | echo "Resolving package..." 8 | swift package resolve 9 | 10 | echo "Getting resolved SwiftyMocky version..." 11 | SWIFTYMOCKY_VERSION=$(swift package show-dependencies | grep -m 1 -Eo 'SwiftyMocky\.git@(.+)>' | grep -oE '\d+.\d+.\d+') 12 | 13 | ./Scripts/install_local_spm_util.sh "https://github.com/krzysztofzablocki/Sourcery.git" "1.8.0" "sourcery" 14 | ./Scripts/install_local_spm_util.sh "https://github.com/nstmrt/SwiftyMocky.git" "$SWIFTYMOCKY_VERSION" "swiftymocky" 15 | 16 | echo "🦄 Cleaning generated Swift files" 17 | for FOLDER in "Sources Tests"; do 18 | find $FOLDER -name '*.generated.swift' -delete 19 | done 20 | echo "✅ Generated Swift files successfully deleted" 21 | 22 | echo "🦄 Generating Mocks..." 23 | PATH="$(pwd)/$SM_UTILS_BIN_PATH/:$PATH" \ 24 | "$SM_UTILS_BIN_PATH/swiftymocky" generate 25 | echo "✅ Tests Mocks successfully generated" 26 | 27 | # echo "🦄 Formatting Mocks..." 28 | # ./format_code Sources/SwiftlaneCoreMocks 29 | # echo "✅ All done!" 30 | -------------------------------------------------------------------------------- /Scripts/format_code.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -euo pipefail 4 | 5 | source ./Scripts/bootstrap_dirs.sh 6 | 7 | SM_SWIFT_FORMAT_NAME="swiftformat" 8 | 9 | ./Scripts/install_local_spm_util.sh "https://github.com/nicklockwood/SwiftFormat.git" "0.49.14" "$SM_SWIFT_FORMAT_NAME" 10 | 11 | SWIFT_VERSION=$(swift -version 2> /dev/null | head -n1 | cut -w -f 4) 12 | 13 | echo "🦄 Starting code formatting ..." 14 | args="$@" 15 | if [ -z "$args" ]; then 16 | args="." 17 | fi 18 | $SM_UTILS_BIN_PATH/$SM_SWIFT_FORMAT_NAME $args --swiftversion "$SWIFT_VERSION" 19 | echo "✅ Code formatting finished" 20 | -------------------------------------------------------------------------------- /Sources/AppStoreConnectAPI/AppStoreConnectAPIClient.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import AppStoreConnectJWT 4 | import Bagbutik_Core 5 | import Combine 6 | import Foundation 7 | import Networking 8 | 9 | public class AppStoreConnectAPIClient: AppStoreConnectAPIClientProtocol { 10 | let bagbutikService: BagbutikServiceProtocol 11 | 12 | public init( 13 | bagbutikService: BagbutikServiceProtocol 14 | ) { 15 | self.bagbutikService = bagbutikService 16 | } 17 | } 18 | 19 | public extension AppStoreConnectAPIClient { 20 | convenience init( 21 | keyId: String, 22 | issuerId: String, 23 | privateKey: String 24 | ) throws { 25 | let urlSession = URLSession(configuration: .ephemeral) 26 | let bagbutikService = BagbutikService( 27 | jwt: try JWT(keyId: keyId, issuerId: issuerId, privateKey: privateKey), 28 | fetchData: urlSession.data(for:delegate:) 29 | ) 30 | 31 | self.init(bagbutikService: bagbutikService) 32 | } 33 | 34 | convenience init( 35 | keyId: String, 36 | issuerId: String, 37 | privateKeyPath: String 38 | ) throws { 39 | let urlSession = URLSession(configuration: .ephemeral) 40 | let bagbutikService = BagbutikService( 41 | jwt: try JWT(keyId: keyId, issuerId: issuerId, privateKeyPath: privateKeyPath), 42 | fetchData: urlSession.data(for:delegate:) 43 | ) 44 | 45 | self.init(bagbutikService: bagbutikService) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Sources/AppStoreConnectAPI/AppStoreConnectAPIDTOs.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Bagbutik_Models 4 | import Foundation 5 | 6 | public enum AppStoreConnectAPIDTOs { 7 | public struct BundleID: Codable, Equatable { 8 | public let name: String 9 | /// bundle id itself. 10 | public let identifier: String 11 | public let appStoreConnectID: String 12 | } 13 | 14 | public struct ReleasedAppStoreVersion { 15 | public let appStoreState: AppStoreVersionState 16 | public let appStoreVersion: String 17 | public let buildVersion: String 18 | public let buildNumber: String 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Sources/AppStoreConnectAPI/BagbutikServiceProtocol.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Bagbutik_Core 4 | import Bagbutik_Models 5 | import Foundation 6 | 7 | extension BagbutikService: BagbutikServiceProtocol {} 8 | 9 | /// Protocol declares functions which are already implemented in `BagbutikService`. 10 | public protocol BagbutikServiceProtocol { 11 | func request(_ request: Request) async throws -> T 12 | func requestAllPages(_ request: Request) async throws 13 | -> (responses: [T], data: [T.Data]) 14 | func requestNextPage(for response: T) async throws -> T? 15 | func requestAllPages(for response: T) async throws -> (responses: [T], data: [T.Data]) 16 | } 17 | -------------------------------------------------------------------------------- /Sources/Git/Options/GitCreateStashOption.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Foundation 4 | 5 | public enum GitCreateStashOption: String, Hashable { 6 | /// All ignored and untracked files are also stashed and then cleaned up with git clean. 7 | case all 8 | 9 | /// All untracked files are also stashed and then cleaned up with git clean. 10 | case includeUntracked 11 | 12 | /// All changes already added to the index are left intact. 13 | case keepIndex 14 | } 15 | -------------------------------------------------------------------------------- /Sources/Git/Options/GitPullOption.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Foundation 4 | 5 | public enum GitPullOption: Hashable, CustomStringConvertible { 6 | /// Fetch all remotes. 7 | case all 8 | 9 | /// Rebase instead of merge if unpushed commits exist. 10 | case rebase(RebaseVariant) 11 | 12 | public enum RebaseVariant: String, Hashable { 13 | /// When false, merge the current branch into the upstream branch. 14 | case `false` 15 | 16 | /// When true, rebase the current branch on top of the upstream branch after fetching. 17 | /// If there is a remote-tracking branch corresponding to the upstream branch 18 | /// and the upstream branch was rebased since last fetched, the rebase 19 | /// uses that information to avoid rebasing non-local changes. 20 | case `true` 21 | 22 | /// When set to merges, rebase using git rebase --rebase-merges 23 | /// so that the local merge commits are included in the rebase. 24 | case merges 25 | 26 | /// When set to preserve (deprecated in favor of merges), rebase with 27 | /// the --preserve-merges option passed to git rebase so that locally created merge commits will not be flattened. 28 | case preserve 29 | 30 | /// When interactive, enable the interactive mode of rebase. 31 | // case interactive 32 | } 33 | 34 | public var description: String { 35 | switch self { 36 | case .all: 37 | return "--all" 38 | case let .rebase(variant): 39 | return "--rebase=" + variant.rawValue 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Sources/GitLabAPI/Deserializer/GitLabAPIDeserializer.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Foundation 4 | import Networking 5 | import SwiftlaneCore 6 | 7 | public final class GitLabAPIDeserializer: DeserializerProtocol { 8 | /// Date parser for GitLab dates. 9 | /// 10 | /// How GitLab provides dates according to their docs: 11 | /// ``` 12 | /// Date time string, ISO 8601 formatted. 13 | /// Example: 2016-03-11T03:45:40Z (requires administrator or project/group owner rights) 14 | /// ``` 15 | private static let dateParser: DateFormatter = .fullISO8601 16 | 17 | private static let decoder: JSONDecoder = { 18 | let decoder = JSONDecoder() 19 | decoder.dateDecodingStrategy = .formatted(dateParser) 20 | decoder.keyDecodingStrategy = .convertFromSnakeCase 21 | return decoder 22 | }() 23 | 24 | public init() {} 25 | 26 | public func deseriaize(_ type: T.Type, from data: Data) throws -> T where T: Decodable { 27 | try Self.decoder.decode(type, from: data) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Sources/GitLabAPI/GitLabAPIClient/GitLabAPIClient.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Combine 4 | import Foundation 5 | import Networking 6 | import SwiftlaneCore 7 | 8 | public class GitLabAPIClient: GitLabAPIClientProtocol { 9 | let client: NetworkingClientProtocol 10 | 11 | public init(networkingClient: NetworkingClientProtocol) { 12 | client = networkingClient 13 | } 14 | } 15 | 16 | public extension GitLabAPIClient { 17 | /// - Parameters: 18 | /// - accessTokenHeaderKey: if you are using `CI_JOB_TOKEN` then set this to `"JOB-TOKEN"` instead of `"PRIVATE-TOKEN"`. 19 | convenience init( 20 | baseURL: URL, 21 | accessToken: String, 22 | accessTokenHeaderKey: String = "PRIVATE-TOKEN", 23 | logLevel: LoggingLevel = .silent, 24 | logger: Logging 25 | ) { 26 | let networkingClient = NetworkingClient( 27 | baseURL: baseURL, 28 | deserializer: GitLabAPIDeserializer(), 29 | logger: NetworkingLogger( 30 | logLevel: logLevel, 31 | logger: logger 32 | ) 33 | ) 34 | 35 | networkingClient.commonHeaders = [ 36 | accessTokenHeaderKey: accessToken, 37 | "Content-Type": "application/json", 38 | ] 39 | 40 | self.init(networkingClient: networkingClient) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Sources/GitLabAPI/GitLabAPIClient/Read/GitLabAPIClient+ApprovalRules.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Combine 4 | import Foundation 5 | import Networking 6 | 7 | /// Functions to work with MR approvals. 8 | public extension GitLabAPIClient { 9 | /// All approval rules (full info) of a project. 10 | func projectApprovalRules( 11 | projectId: Int 12 | ) -> AnyPublisher<[MergeRequestApprovalRule], NetworkingError> { 13 | client 14 | .get("projects/\(projectId)/approval_rules") 15 | .perform() 16 | } 17 | 18 | /// All approval rules (full info) of a Merge Request. 19 | func mergeRequestApprovalRulesAll( 20 | projectId: Int, 21 | mergeRequestIid: Int 22 | ) -> AnyPublisher<[MergeRequestApprovalRule], NetworkingError> { 23 | client 24 | .get("projects/\(projectId)/merge_requests/\(mergeRequestIid)/approval_rules") 25 | .perform() 26 | } 27 | 28 | /// Information about current state of approval rules of a Merge Request. 29 | /// `approvalRulesLeft` containts only partial info (id and name). 30 | func mergeRequestApprovalRulesLeft( 31 | projectId: Int, 32 | mergeRequestIid: Int 33 | ) -> AnyPublisher { 34 | client 35 | .get("projects/\(projectId)/merge_requests/\(mergeRequestIid)/approvals") 36 | .perform() 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Sources/GitLabAPI/GitLabAPIClient/Read/GitLabAPIClient+Pipelines.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Combine 4 | import Foundation 5 | import Networking 6 | 7 | /// Working with pipelines. 8 | public extension GitLabAPIClient { 9 | /// Load pipeline info. 10 | func pipeline( 11 | projectId: Int, 12 | pipelineId: Int 13 | ) -> AnyPublisher { 14 | client 15 | .get("projects/\(projectId)/pipelines/\(pipelineId)") 16 | .perform() 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Sources/GitLabAPI/GitLabAPIClient/Read/GitLabAPIClient+Repository.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Combine 4 | import Foundation 5 | import Networking 6 | 7 | /// Working with files in a repository. 8 | public extension GitLabAPIClient { 9 | /// Load file content. 10 | /// - Parameters: 11 | /// - path: file path relative to repo root. 12 | /// - ref: git ref (branch name or commit sha). 13 | func loadRepositoryFile( 14 | path: String, 15 | projectId: Int, 16 | ref: String = "master" 17 | ) -> AnyPublisher { 18 | let escapedPath = path.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed) ?? path 19 | return client 20 | .get("projects/\(projectId)/repository/files/\(escapedPath)") 21 | .with(queryItems: ["ref": ref]) 22 | .perform() 23 | } 24 | 25 | /// Load Diff between two refs (the same diff as if we create a merge request `source -> target`). 26 | /// - Parameters: 27 | /// - projectId: gitlab project (repo) id. 28 | /// - source: The commit SHA or branch name. 29 | /// - target: The commit SHA or branch name. 30 | func repositoryDiff( 31 | projectId: Int, 32 | source: String, 33 | target: String 34 | ) -> AnyPublisher { 35 | client 36 | .get("projects/\(projectId)/repository/compare") 37 | .with(queryItems: [ 38 | "from": target, 39 | "to": source, 40 | ]) 41 | .perform() 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Sources/GitLabAPI/GitLabAPIClient/Read/GitLabAPIClient+Users.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Combine 4 | import Foundation 5 | import Networking 6 | 7 | /// Working with users. 8 | public extension GitLabAPIClient { 9 | func userActivityEvents( 10 | userId: Int, 11 | after: Date, 12 | page: Int, 13 | perPage: Int 14 | ) -> AnyPublisher<[UserActivityEvent], NetworkingError> { 15 | client 16 | .get("users/\(userId)/events") 17 | .with(queryItems: [ 18 | "page": page, 19 | "per_page": perPage, 20 | "after": after.shortISO8601String, 21 | ]) 22 | .perform() 23 | } 24 | 25 | func groupMembers( 26 | group: GitLab.Group 27 | ) -> AnyPublisher<[Member], NetworkingError> { 28 | client 29 | .get("groups/\(group.id)/members") 30 | .with(queryItems: ["per_page": 100]) 31 | .perform() 32 | } 33 | 34 | func groupDetails( 35 | group: GitLab.Group 36 | ) -> AnyPublisher { 37 | // https://docs.gitlab.com/ee/api/groups.html#details-of-a-group 38 | client 39 | .get("groups/\(group.id)") 40 | .perform() 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Sources/GitLabAPI/GitLabAPIClient/Write/GitLabAPIClient+EditPipelines.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Combine 4 | import Foundation 5 | import Networking 6 | 7 | /// Creating and manipulating pipelines. 8 | public extension GitLabAPIClient { 9 | func createPipeline( 10 | projectId: Int, 11 | bodyModel: CreatePipeline 12 | ) -> AnyPublisher { 13 | client 14 | .post("projects/\(projectId)/pipeline") 15 | .with(body: bodyModel) 16 | .perform() 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Sources/GitLabAPI/GitLabAPIClient/Write/GitLabAPIClient+EditRepository.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Combine 4 | import Foundation 5 | import Networking 6 | 7 | /// Modifying repo files. 8 | public extension GitLabAPIClient { 9 | /// Update file contents. 10 | func updateRepositoryFile( 11 | path: String, 12 | projectId: Int, 13 | bodyModel: UpdateFileContent 14 | ) -> AnyPublisher { 15 | let escapedPath = path.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed) ?? path 16 | return client 17 | .put("projects/\(projectId)/repository/files/\(escapedPath)") 18 | .with(body: bodyModel) 19 | .perform() 20 | } 21 | 22 | /// Create a new branch. 23 | /// - Parameters: 24 | /// - branchName: name of new branch. 25 | /// - ref: git ref (another branch name or commit sha) from where branch should be created. 26 | func createBranch( 27 | projectId: Int, 28 | branchName: String, 29 | ref: String 30 | ) -> AnyPublisher { 31 | client 32 | .post("projects/\(projectId)/repository/branches") 33 | .with(queryItems: [ 34 | "branch": branchName, 35 | "ref": ref, 36 | ]) 37 | .perform() 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Sources/GitLabAPI/GitLabAPIComplexClient/GitLabAPIComplexClientProtocol.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Combine 4 | import Foundation 5 | import Networking 6 | 7 | protocol GitLabAPIComplexClientProtocol { 8 | // MARK: - GitLabAPIClient+PotentialApprovers 9 | 10 | /// Load list of users who did not yet approve a Merge Request. 11 | /// 12 | /// 1) Load approval rules left (partial info) for the merge request. 13 | /// 2) Load full info about these rules using two requests: 14 | /// * all rules of the merge request; 15 | /// * all rules of the project. 16 | /// 3) Extract list of users from loaded full info about approval rules left. 17 | /// 4) Append assignees to (3). 18 | /// 5) Drop from (4) those users who already approved the merge request. 19 | /// 20 | func findPotentialApprovers(for mr: MergeRequest) -> AnyPublisher, NetworkingError> 21 | 22 | /// Recursively load all evens of a user. 23 | /// 24 | /// Note: Pages in GitLab API are indexed starting from `1`. 25 | func userActivityEventsFromAllPages( 26 | userId: Int, 27 | afterDate: Date, 28 | startingFromPage page: Int 29 | ) -> AnyPublisher<[UserActivityEvent], NetworkingError> 30 | } 31 | -------------------------------------------------------------------------------- /Sources/GitLabAPI/Models/CommitInfo.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Foundation 4 | 5 | public struct CommitInfo: Decodable { 6 | public let id: String 7 | public let shortId: String 8 | public let title: String 9 | public let authorName: String 10 | public let authorEmail: String 11 | public let createdAt: String 12 | public let webUrl: String? 13 | } 14 | -------------------------------------------------------------------------------- /Sources/GitLabAPI/Models/FileContent.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Foundation 4 | 5 | public struct FileContent: Codable { 6 | public let commitId: String 7 | public let content: Data 8 | } 9 | 10 | public struct UpdateFileContent: Encodable { 11 | public let branch: String 12 | public let commitMessage: String 13 | public let content: String 14 | 15 | public init(branch: String, commitMessage: String, content: String) { 16 | self.branch = branch 17 | self.commitMessage = commitMessage 18 | self.content = content 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Sources/GitLabAPI/Models/FileDiff.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Foundation 4 | 5 | public struct FileDiff: Decodable { 6 | public init( 7 | oldPath: String, 8 | newPath: String, 9 | aMode: String, 10 | bMode: String, 11 | newFile: Bool, 12 | renamedFile: Bool, 13 | deletedFile: Bool, 14 | diff: String 15 | ) { 16 | self.oldPath = oldPath 17 | self.newPath = newPath 18 | self.aMode = aMode 19 | self.bMode = bMode 20 | self.newFile = newFile 21 | self.renamedFile = renamedFile 22 | self.deletedFile = deletedFile 23 | self.diff = diff 24 | } 25 | 26 | public let oldPath: String 27 | public let newPath: String 28 | public let aMode: String 29 | public let bMode: String 30 | public let newFile: Bool 31 | public let renamedFile: Bool 32 | public let deletedFile: Bool 33 | public let diff: String 34 | } 35 | -------------------------------------------------------------------------------- /Sources/GitLabAPI/Models/GitLabSpace.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Foundation 4 | 5 | public enum GitLab { 6 | public struct Group { 7 | public let id: Int 8 | 9 | public init(id: Int) { 10 | self.id = id 11 | } 12 | } 13 | 14 | public struct Project { 15 | public let id: Int 16 | 17 | public init(id: Int) { 18 | self.id = id 19 | } 20 | } 21 | 22 | /// GitLab space. 23 | public enum Space { 24 | case group(id: Int) 25 | case project(id: Int) 26 | 27 | /// ! Caution: Some API methods do not support this. 28 | case everywhere 29 | 30 | func apiPath(suffix: String) -> String { 31 | switch self { 32 | case let .group(id): 33 | return "groups/\(id)/" + suffix 34 | case let .project(id): 35 | return "projects/\(id)/" + suffix 36 | case .everywhere: 37 | return suffix 38 | } 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Sources/GitLabAPI/Models/Group.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Foundation 4 | 5 | /// Note: Model is not full. Parsed from response of https://docs.gitlab.com/ee/api/groups.html#details-of-a-group 6 | public struct Group: Decodable, Hashable { 7 | public let id: Int 8 | public let name: String 9 | public let path: String 10 | public let description: String 11 | public let avatarUrl: String? 12 | public let webUrl: String? 13 | 14 | public init(id: Int, name: String, path: String, description: String, avatarUrl: String?, webUrl: String?) { 15 | self.id = id 16 | self.name = name 17 | self.path = path 18 | self.description = description 19 | self.avatarUrl = avatarUrl 20 | self.webUrl = webUrl 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Sources/GitLabAPI/Models/Member.swift: -------------------------------------------------------------------------------- 1 | 2 | // MARK: - Member 3 | 4 | public struct Member: Decodable, Hashable { 5 | public let id: Int 6 | public let name: String 7 | public let username: String 8 | public let state: String 9 | public let avatarUrl: String? 10 | public let webUrl: String? 11 | 12 | public init( 13 | id: Int, 14 | name: String, 15 | username: String, 16 | state: String, 17 | avatarUrl: String?, 18 | webUrl: String? 19 | ) { 20 | self.id = id 21 | self.name = name 22 | self.username = username 23 | self.state = state 24 | self.avatarUrl = avatarUrl 25 | self.webUrl = webUrl 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Sources/GitLabAPI/Models/MergeRequest.Changes.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Foundation 4 | 5 | public extension MergeRequest { 6 | struct Changes: Decodable { 7 | public init(changes: [FileDiff]?) { 8 | self.changes = changes 9 | } 10 | 11 | public let changes: [FileDiff]? 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Sources/GitLabAPI/Models/MergeRequest.Commit.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Foundation 4 | 5 | public extension MergeRequest { 6 | struct Commit: Codable { 7 | public let longSHA: String 8 | public let shortSHA: String 9 | public let createdAt: String 10 | public let parentIds: [String] 11 | public let title: String 12 | public let message: String 13 | public let authorName: String 14 | public let authorEmail: String 15 | public let authoredDate: String 16 | public let committerName: String 17 | public let committerEmail: String 18 | public let committedDate: String 19 | public let webUrl: String 20 | 21 | enum CodingKeys: String, CodingKey { 22 | case longSHA = "id" 23 | case shortSHA = "shortId" 24 | case createdAt 25 | case parentIds 26 | case title 27 | case message 28 | case authorName 29 | case authorEmail 30 | case authoredDate 31 | case committerName 32 | case committerEmail 33 | case committedDate 34 | case webUrl 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Sources/GitLabAPI/Models/MergeRequest.Note.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Foundation 4 | 5 | public extension MergeRequest { 6 | struct Note: Decodable { 7 | public let id: Int 8 | public let body: String 9 | // public let attachment: Any 10 | public let author: Member 11 | public let createdAt: Date 12 | public let updatedAt: Date 13 | public let system: Bool 14 | public let noteableId: Int 15 | public let noteableType: String 16 | public let noteableIid: Int 17 | public let resolvable: Bool 18 | public let confidential: Bool 19 | 20 | public init( 21 | id: Int, 22 | body: String, 23 | author: Member, 24 | createdAt: Date, 25 | updatedAt: Date, 26 | system: Bool, 27 | noteableId: Int, 28 | noteableType: String, 29 | noteableIid: Int, 30 | resolvable: Bool, 31 | confidential: Bool 32 | ) { 33 | self.id = id 34 | self.body = body 35 | self.author = author 36 | self.createdAt = createdAt 37 | self.updatedAt = updatedAt 38 | self.system = system 39 | self.noteableId = noteableId 40 | self.noteableType = noteableType 41 | self.noteableIid = noteableIid 42 | self.resolvable = resolvable 43 | self.confidential = confidential 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Sources/GitLabAPI/Models/MergeRequest.State.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Foundation 4 | 5 | public extension MergeRequest { 6 | enum State: String { 7 | case opened, close, locked, merged, all 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Sources/GitLabAPI/Models/MergeRequest.User.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Foundation 4 | 5 | public extension MergeRequest { 6 | struct User: Decodable { 7 | public let canMerge: Bool 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Sources/GitLabAPI/Models/MergeRequest.swift: -------------------------------------------------------------------------------- 1 | 2 | import Foundation 3 | 4 | // MARK: - MergeRequest 5 | 6 | public struct MergeRequest: Decodable { 7 | public let id: Int 8 | public let iid: Int 9 | public let projectId: Int 10 | public let title: String 11 | public let description: String? 12 | public let state: String 13 | public let createdAt: Date 14 | public let updatedAt: Date 15 | public let targetBranch: String 16 | public let sourceBranch: String 17 | public let userNotesCount: Int? 18 | public let upvotes: Int 19 | public let downvotes: Int 20 | public let author: Member? 21 | public let assignees: [Member] 22 | public let assignee: Member? 23 | public let reviewers: [Member] 24 | public let sourceProjectId: Int 25 | public let targetProjectId: Int 26 | public let labels: [String] 27 | public let workInProgress: Bool 28 | public let mergeWhenPipelineSucceeds: Bool 29 | public let mergeStatus: String? 30 | public let sha: String? 31 | public let mergeCommitSha: String? 32 | public let squashCommitSha: String? 33 | public let discussionLocked: Bool? 34 | public let shouldRemoveSourceBranch: Bool? 35 | public let forceRemoveSourceBranch: Bool? 36 | public let webUrl: String? 37 | public let squash: Bool 38 | public let hasConflicts: Bool 39 | public let blockingDiscussionsResolved: Bool 40 | public let changesCount: String? 41 | public let user: MergeRequest.User? 42 | public let changes: [FileDiff]? 43 | } 44 | -------------------------------------------------------------------------------- /Sources/GitLabAPI/Models/MergeRequestApprovalRule.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Foundation 4 | 5 | public struct MergeRequestApprovalRule: Decodable { 6 | public let id: Int 7 | public let name: String 8 | public let ruleType: String 9 | public let eligibleApprovers: [Member]? 10 | public let approvalsRequired: Int? 11 | } 12 | -------------------------------------------------------------------------------- /Sources/GitLabAPI/Models/MergeRequestApprovals.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Foundation 4 | 5 | public struct MergeRequestApprovals: Decodable { 6 | public let approvalRulesLeft: [MergeRequestApprovalRule] 7 | public let approvedBy: [ApprovedByContainer] 8 | public let approvalsLeft: Int 9 | 10 | public struct ApprovedByContainer: Decodable { 11 | public let user: Member? 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Sources/GitLabAPI/Models/Package.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Foundation 4 | 5 | public struct Package: Codable { 6 | public let id: Int 7 | public let name: String 8 | public let version: String 9 | public let packageType: PackageType 10 | public let status: Status 11 | public let _links: Links 12 | public let createdAt: Date 13 | public let tags: [String] 14 | } 15 | 16 | public struct PackageUploadResult: Codable { 17 | public let message: String 18 | 19 | public static let successMessage = "201 Created" 20 | } 21 | 22 | public extension Package { 23 | /// Filter the returned packages by type. 24 | enum PackageType: String, Codable { 25 | case conan 26 | case maven 27 | case npm 28 | case pypi 29 | case composer 30 | case nuget 31 | case helm 32 | case terraformModule = "terraform_module" 33 | case golang 34 | case generic 35 | } 36 | 37 | /// Filter the returned packages by status. 38 | enum Status: String, Codable { 39 | case `default` 40 | case hidden 41 | /// ⚠️ Working with packages that have a processing status 42 | /// can result in malformed data or broken packages. 43 | case processing 44 | case error 45 | case pendingDestruction = "pending_destruction" 46 | } 47 | 48 | struct Links: Codable { 49 | /// e.g. `"/ios/ios-app-packages/-/packages/403"` 50 | public let webPath: String 51 | /// e.g. `"https://gitlab.com/api/v4/projects/844/packages/403"` 52 | public let deleteApiPath: String 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Sources/GitLabAPI/Models/Pipeline.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Foundation 4 | 5 | public struct Pipeline: Decodable { 6 | public let id: Int 7 | public let iid: Int 8 | public let projectId: Int 9 | public let sha: String 10 | public let ref: String 11 | public let status: String 12 | public let source: String 13 | public let createdAt: Date 14 | public let updatedAt: Date 15 | public let webUrl: String? 16 | public let beforeSha: String 17 | public let tag: Bool 18 | public let user: Member 19 | public let startedAt: Date? 20 | public let finishedAt: Date? 21 | } 22 | 23 | public struct CreatePipeline: Encodable { 24 | public struct Variable: Encodable { 25 | public let key: String 26 | public let value: String 27 | 28 | public init(key: String, value: String) { 29 | self.key = key 30 | self.value = value 31 | } 32 | } 33 | 34 | public let ref: String 35 | public let variables: [Variable]? 36 | 37 | public init(ref: String, variables: [Variable]?) { 38 | self.ref = ref 39 | self.variables = variables 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Sources/GitLabAPI/Models/RepositoryCompareResult.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Foundation 4 | 5 | public struct RepositoryCompareResult: Decodable { 6 | public let commits: [CommitInfo] 7 | public let diffs: [FileDiff] 8 | public let compareTimeout: Bool 9 | public let compareSameRef: Bool 10 | public let webUrl: String? 11 | } 12 | -------------------------------------------------------------------------------- /Sources/Guardian/Services/MergeRequestReportFactory/MergeRequestReportCaptionProvider.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Foundation 4 | 5 | public protocol MergeRequestReportCaptionProviding { 6 | func reportCaption(commitSHA: String) -> String 7 | } 8 | 9 | public class MergeRequestReportCaptionProvider: MergeRequestReportCaptionProviding { 10 | private let dateFormatter: DateFormatter 11 | private let ciToolName: String 12 | 13 | public init(dateFormatter: DateFormatter, ciToolName: String) { 14 | self.dateFormatter = dateFormatter 15 | self.ciToolName = ciToolName 16 | } 17 | 18 | public convenience init(ciToolName: String) { 19 | let formatter = DateFormatter() 20 | formatter.locale = Locale(identifier: "EN_us") 21 | formatter.dateFormat = "HH:mm:ss.SSS zzz" 22 | self.init(dateFormatter: formatter, ciToolName: ciToolName) 23 | } 24 | 25 | public func reportCaption(commitSHA: String) -> String { 26 | """ 27 |

28 | Generated by 🦾🤖 29 | \(ciToolName) 30 | against \(commitSHA) 31 | at \(dateFormatter.string(from: Date())) 32 |

33 | """ 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Sources/Guardian/Services/MergeRequestReportFactory/MergeRequestReportFactoring.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Foundation 4 | 5 | // sourcery: AutoMockable 6 | public protocol MergeRequestReportFactoring { 7 | func reportBody( 8 | fails: [String], 9 | warns: [String], 10 | messages: [String], 11 | markdowns: [String], 12 | successes: [String], 13 | invisibleMark: String, 14 | commitSHA: String 15 | ) -> String 16 | } 17 | -------------------------------------------------------------------------------- /Sources/Guardian/Services/MergeRequestReportFactory/MergeRequestReportFactory.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Foundation 4 | import SwiftlaneCore 5 | 6 | // swiftformat:disable indent 7 | public class MergeRequestReportFactory: MergeRequestReportFactoring { 8 | private let captionProvider: MergeRequestReportCaptionProviding 9 | 10 | public init(captionProvider: MergeRequestReportCaptionProviding) { 11 | self.captionProvider = captionProvider 12 | } 13 | 14 | public func reportBody( 15 | fails: [String], 16 | warns: [String], 17 | messages: [String], 18 | markdowns: [String], 19 | successes: [String], 20 | invisibleMark: String, 21 | commitSHA: String 22 | ) -> String { 23 | let failsTable = MarkdownTableBuilder( 24 | rows: fails.map { 25 | ["⛔️", $0] 26 | } 27 | ) 28 | 29 | let warnsTable = MarkdownTableBuilder( 30 | rows: warns.map { 31 | ["⚠️", $0] 32 | } 33 | ) 34 | 35 | let successesTable = MarkdownTableBuilder( 36 | rows: successes.map { 37 | ["✅", $0] 38 | } 39 | ) 40 | 41 | let messagesTable = MarkdownTableBuilder( 42 | rows: messages.map { 43 | ["📝", $0] 44 | } 45 | ) 46 | 47 | let report = 48 | """ 49 | \(invisibleMark) 50 | 51 | \(failsTable.build(title: "## Failures")) 52 | 53 | \(warnsTable.build(title: "## Warnings")) 54 | 55 | \(messagesTable.build(title: "## Messages")) 56 | 57 | \(successesTable.build(title: "## Success")) 58 | 59 | ## Markdowns 60 | \(markdowns.joined(separator: "\n\n")) 61 | 62 | \(captionProvider.reportCaption(commitSHA: commitSHA)) 63 | """ 64 | 65 | return report 66 | } 67 | } 68 | 69 | // swiftformat:enable indent 70 | -------------------------------------------------------------------------------- /Sources/Guardian/Services/MergeRequestReporter/MergeRequestReporting.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | // sourcery: AutoMockable 4 | public protocol MergeRequestReporting { 5 | func createOrUpdateReport() throws 6 | 7 | func warn(_ markdown: String) 8 | 9 | func fail(_ markdown: String) 10 | 11 | func message(_ markdown: String) 12 | 13 | func markdown(_ markdown: String) 14 | 15 | func success(_ markdown: String) 16 | 17 | func hasFails() -> Bool 18 | } 19 | -------------------------------------------------------------------------------- /Sources/JiraAPI/Deserializer/JiraAPIDeserializer.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Foundation 4 | import Networking 5 | 6 | public final class JiraAPIDeserializer: DeserializerProtocol { 7 | private static let decoder: JSONDecoder = { 8 | let decoder = JSONDecoder() 9 | decoder.dateDecodingStrategy = .formatted(DateFormatter.yyyyMMdd_dashSeparated) 10 | decoder.keyDecodingStrategy = .useDefaultKeys 11 | return decoder 12 | }() 13 | 14 | public init() {} 15 | 16 | public func deseriaize(_ type: T.Type, from data: Data) throws -> T where T: Decodable { 17 | try Self.decoder.decode(type, from: data) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Sources/JiraAPI/Deserializer/JiraAPISerializer.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Foundation 4 | import Networking 5 | 6 | public final class JiraAPISerializer: SerializerProtocol { 7 | private let encoder: JSONEncoder = { 8 | let encoder = JSONEncoder() 9 | encoder.dateEncodingStrategy = .deferredToDate 10 | encoder.keyEncodingStrategy = .useDefaultKeys 11 | return encoder 12 | }() 13 | 14 | public init() {} 15 | 16 | public func serialize(_ object: T) throws -> Data where T: Encodable { 17 | try encoder.encode(object) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Sources/JiraAPI/Models/AsceticVersion.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Foundation 4 | 5 | public struct AsceticVersion: Codable, Hashable { 6 | public init(name: String) { 7 | self.name = name 8 | } 9 | 10 | public let name: String 11 | 12 | enum CodingKeys: String, CodingKey { 13 | case name 14 | } 15 | } 16 | 17 | extension AsceticVersion: ExpressibleByStringLiteral { 18 | public init(stringLiteral value: String) { 19 | self.init(name: value) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Sources/JiraAPI/Models/DetailVersion.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Foundation 4 | 5 | public struct DetailVersion: Codable { 6 | public let linkString: String 7 | public let id: String 8 | public let name: String 9 | // public let issusesStatus: IssuesStatus // In API V2 not found 10 | 11 | enum CodingKeys: String, CodingKey { 12 | case name, id 13 | case linkString = "self" 14 | // case issusesStatus = "issuesStatusForFixVersion" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Sources/JiraAPI/Models/FullVersion.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Foundation 4 | 5 | public struct FullVersion: Codable, Hashable { 6 | public let linkString: String 7 | public let id: String 8 | public let name: String 9 | public let archived: Bool 10 | public let released: Bool 11 | public let startDate: Date? 12 | public let releaseDate: Date? 13 | 14 | enum CodingKeys: String, CodingKey { 15 | case id, name, archived, released, startDate, releaseDate 16 | case linkString = "self" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Sources/JiraAPI/Models/Issue.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Foundation 4 | 5 | public struct Issue: Codable { 6 | public let id: String 7 | public let key: String 8 | public let fields: Fields 9 | 10 | public struct IssueType: Codable { 11 | public let name: String 12 | } 13 | 14 | public struct SubtasksFields: Codable { 15 | public let issuetype: IssueType 16 | } 17 | 18 | public struct Subtasks: Codable { 19 | public let fields: SubtasksFields 20 | } 21 | 22 | public struct ParentTask: Codable { 23 | public let id: String 24 | public let key: String 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Sources/JiraAPI/Models/IssuesStatus.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Foundation 4 | 5 | public struct IssuesStatus: Codable { 6 | public let unmapped: Int 7 | public let toDo: Int 8 | public let inProgress: Int 9 | public let done: Int 10 | 11 | public var count: Int { 12 | unmapped + toDo + inProgress + done 13 | } 14 | 15 | public var percentDone: Int { 16 | Int(Double(done) / Double(count) * 100) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Sources/JiraAPI/Models/JiraJQL.swift: -------------------------------------------------------------------------------- 1 | 2 | import Foundation 3 | 4 | public final class JiraJQL { 5 | public var jqlString: String 6 | 7 | public init(projectKey: String) { 8 | jqlString = "project = \(projectKey)" 9 | } 10 | 11 | public init(jqlString: String) { 12 | self.jqlString = jqlString 13 | } 14 | } 15 | 16 | public extension JiraJQL { 17 | @discardableResult 18 | func status(in statuses: [T]) -> JiraJQL { 19 | jqlString += " AND status in (" + statuses.map(\.description).joined(separator: ", ") + ")" 20 | return self 21 | } 22 | 23 | @discardableResult 24 | func version(_ version: String) -> JiraJQL { 25 | jqlString += " AND fixVersion = \(version)" 26 | return self 27 | } 28 | 29 | @discardableResult 30 | func type(in types: [T]) -> JiraJQL { 31 | jqlString += " AND type in (" + types.map(\.description).joined(separator: ", ") + ")" 32 | return self 33 | } 34 | 35 | @discardableResult 36 | func labels(_ label: String) -> JiraJQL { 37 | jqlString += " AND labels = \(label)" 38 | return self 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Sources/JiraAPI/Models/JiraSearch.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Foundation 4 | 5 | public struct JiraSearch: Encodable { 6 | let jql: String 7 | let startAt: Int = 0 8 | let maxResults: Int = 500 9 | let fields: [String] = [] 10 | // let fieldsByKeys = false // In API V2 not found 11 | } 12 | -------------------------------------------------------------------------------- /Sources/JiraAPI/Models/Priority.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Foundation 4 | 5 | public struct Priority: Codable { 6 | public let name: String 7 | public let id: String 8 | public let linkString: String 9 | 10 | enum CodingKeys: String, CodingKey { 11 | case name, id 12 | case linkString = "self" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Sources/JiraAPI/Models/Project.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Foundation 4 | 5 | /// Jira project info. 6 | /// 7 | /// Note: This model does not parse all the data return by the API. 8 | public struct Project: Codable { 9 | public let id: String 10 | public let name: String 11 | public let versions: [FullVersion] 12 | } 13 | -------------------------------------------------------------------------------- /Sources/JiraAPI/Models/SearchResult.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Foundation 4 | 5 | public struct SearchResult: Codable { 6 | public let expand: String? 7 | public let startAt: Int 8 | public let maxResults: Int 9 | public let total: Int 10 | public let issues: [Issue] 11 | } 12 | -------------------------------------------------------------------------------- /Sources/JiraAPI/Models/Status.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Foundation 4 | 5 | public struct Status: Codable { 6 | public let description: String 7 | public let id: String 8 | public let name: String 9 | public let linkString: String 10 | 11 | enum CodingKeys: String, CodingKey { 12 | case description, id, name 13 | case linkString = "self" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Sources/JiraAPI/Models/Transition.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | public struct Transition: Codable { 4 | public let id: String 5 | public let name: String 6 | public let targetStatus: Status 7 | 8 | public init( 9 | id: String, 10 | targetStatus: Status, 11 | name: String = "" 12 | ) { 13 | self.id = id 14 | self.targetStatus = targetStatus 15 | self.name = name 16 | } 17 | 18 | enum CodingKeys: String, CodingKey { 19 | case id 20 | case name 21 | case targetStatus = "to" 22 | } 23 | 24 | enum EncodingKeys: String, CodingKey { 25 | case id 26 | } 27 | 28 | public func encode(to encoder: Encoder) throws { 29 | var container = encoder.container(keyedBy: EncodingKeys.self) 30 | try container.encode(id, forKey: .id) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Sources/JiraAPI/Models/TransitionsResponse.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Foundation 4 | 5 | public struct TransitionsResponse: Decodable { 6 | public let transitions: [Transition] 7 | } 8 | -------------------------------------------------------------------------------- /Sources/JiraAPI/Models/User.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Foundation 4 | 5 | public struct User: Codable { 6 | public let key: String 7 | public let displayName: String 8 | public let emailAddress: String 9 | public let linkString: String 10 | 11 | enum CodingKeys: String, CodingKey { 12 | case key, displayName, emailAddress 13 | case linkString = "self" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Sources/MattermostAPI/MattermostAPIClient.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Combine 4 | import Foundation 5 | import Networking 6 | import SwiftlaneCore 7 | 8 | public class MattermostAPIClient { 9 | let client: NetworkingClient 10 | 11 | public init(client: NetworkingClient) { 12 | self.client = client 13 | } 14 | 15 | public convenience init(baseURL: URL, logger: Logging, logLevel: LoggingLevel = .silent) { 16 | let client = NetworkingClient( 17 | baseURL: baseURL, 18 | configuration: .default, 19 | logger: NetworkingLogger( 20 | logLevel: logLevel, 21 | logger: logger 22 | ) 23 | ) 24 | client.commonHeaders = ["Content-Type": "application/json"] 25 | self.init(client: client) 26 | } 27 | 28 | /// https://developers.mattermost.com/integrate/webhooks/incoming/ 29 | public func postWebhook(hookKey: String, body: T) throws -> AnyPublisher { 30 | client.post("hooks/" + hookKey) 31 | .with(body: body) 32 | .perform() 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Sources/Networking/Extensions/AnyEncodable.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Foundation 4 | 5 | public extension Encodable { 6 | func eraseToAnyEncodable() -> AnyEncodable { 7 | AnyEncodable(self) 8 | } 9 | } 10 | 11 | public class AnyEncodable: Encodable { 12 | private let object: Any 13 | private let encode: (Encoder) throws -> Void 14 | 15 | public init(_ object: T) { 16 | self.object = object 17 | encode = object.encode(to:) 18 | } 19 | 20 | public func encode(to encoder: Encoder) throws { 21 | try encode(encoder) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Sources/Networking/Extensions/DecodingError+.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Foundation 4 | 5 | public extension DecodingError { 6 | var description: String { 7 | switch self { 8 | case let .typeMismatch(_, value): 9 | return "typeMismatch error: \(value.debugDescription) \(localizedDescription)" 10 | case let .valueNotFound(_, value): 11 | return "valueNotFound error: \(value.debugDescription) \(localizedDescription)" 12 | case let .keyNotFound(_, value): 13 | return "keyNotFound error: \(value.debugDescription) \(localizedDescription)" 14 | case let .dataCorrupted(key): 15 | return "dataCorrupted error at: \(key) \(localizedDescription)" 16 | default: 17 | return "decoding error: \(localizedDescription)" 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Sources/Networking/Extensions/JSONDecoder+.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Foundation 4 | 5 | public extension JSONDecoder { 6 | static var snakeCaseConverting: JSONDecoder { 7 | let decoder = JSONDecoder() 8 | decoder.keyDecodingStrategy = .convertFromSnakeCase 9 | return decoder 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Sources/Networking/Extensions/JSONEncoder+.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Foundation 4 | 5 | public extension JSONEncoder { 6 | static var snakeCaseConverting: JSONEncoder { 7 | let encoder = JSONEncoder() 8 | encoder.keyEncodingStrategy = .convertToSnakeCase 9 | return encoder 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Sources/Networking/Extensions/ProgressUserInfoKey+.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Foundation 4 | 5 | extension ProgressUserInfoKey { 6 | // This key is not even mentioned in Apple's docs but it exists... 7 | static var byteCompletedCountKey: ProgressUserInfoKey { 8 | ProgressUserInfoKey(rawValue: "NSProgressByteCompletedCountKey") 9 | } 10 | 11 | // This key is not even mentioned in Apple's docs but it exists... 12 | static var byteTotalCountKey: ProgressUserInfoKey { 13 | ProgressUserInfoKey(rawValue: "NSProgressByteTotalCountKey") 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Sources/Networking/Models/DataTaskInfo.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Foundation 4 | 5 | public enum DataTaskInfo { 6 | public typealias ProgressObject = URLSessionDataTask 7 | 8 | case progress(URLSessionDataTask) 9 | case result(data: Data, response: URLResponse) 10 | 11 | public var progress: ProgressObject? { 12 | switch self { 13 | case let .progress(value): 14 | return value 15 | case .result: 16 | return nil 17 | } 18 | } 19 | 20 | public var result: (data: Data, response: URLResponse)? { 21 | switch self { 22 | case .progress: 23 | return nil 24 | case let .result(data, response): 25 | return (data, response) 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Sources/Networking/Models/HTTPMethod.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Foundation 4 | 5 | public enum HTTPMethod: String, Codable, Equatable { 6 | case get = "GET" 7 | case put = "PUT" 8 | case patch = "PATCH" 9 | case post = "POST" 10 | case delete = "DELETE" 11 | } 12 | -------------------------------------------------------------------------------- /Sources/Networking/Models/NetworkingProgress.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Foundation 4 | 5 | public struct NetworkingProgress: Hashable, Comparable, Codable { 6 | /// Correct value is guarantied. 7 | public let fractionCompleted: Double 8 | /// Correct value is NOT guarantied. 9 | public let completedBytes: Int64 10 | /// Correct value is NOT guarantied. 11 | public let totalBytes: Int64 12 | 13 | public static func < (lhs: NetworkingProgress, rhs: NetworkingProgress) -> Bool { 14 | lhs.fractionCompleted < rhs.fractionCompleted 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Sources/Networking/Models/NetworkingRequest/NetworkingRequest.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Combine 4 | import Foundation 5 | 6 | public struct NetworkingRequest: CustomStringConvertible { 7 | public let baseURL: URL 8 | public let route: String 9 | public let method: HTTPMethod 10 | public var headers: [String: String] = [:] 11 | public var queryItems: [String: Any] = [:] 12 | public var body: Data? 13 | public var timeout: TimeInterval? 14 | 15 | public var description: String { 16 | let loggedBody = body.map { String(data: $0, encoding: .utf8) ?? $0.description } 17 | return "NetworkingRequest:\n" + [ 18 | "baseURL: \(baseURL.description.quoted)", 19 | "route: \(route.quoted)", 20 | "fullURL: \(buildURLRequest(logger: nil).url?.absoluteString ?? "nil")", 21 | "method: \(method.rawValue.quoted)", 22 | "headers: \(headers.mapValues { _ in "
" }.asPrettyJSON())", 23 | "queryItems: \(queryItems)", 24 | "body: \(loggedBody?.quoted ?? "nil")", 25 | "timeout: \(timeout?.description ?? "nil")", 26 | ].joined(separator: "\n").addPrefixToAllLines("\t") 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Sources/Networking/Models/NetworkingResponse.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Foundation 4 | 5 | public struct NetworkingResponse: CustomStringConvertible { 6 | public let request: NetworkingRequest 7 | public let urlRequest: URLRequest 8 | public let urlResponse: HTTPURLResponse 9 | public let data: Data 10 | 11 | public var status: ResponseStatus { 12 | .make(for: urlResponse.statusCode) 13 | } 14 | 15 | public var description: String { 16 | "NetworkingResponse:\n" + [ 17 | "\(request)", 18 | "Status code: \(status.rawValue) (\(status))", 19 | "Response body: " + (String(data: data, encoding: .utf8)?.quoted ?? data.description), 20 | ].joined(separator: "\n").addPrefixToAllLines("\t") 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Sources/Networking/Models/ProgressOrResult.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Combine 4 | import Foundation 5 | 6 | public typealias NetworkingProgressOrResponse = ProgressOrResult 7 | 8 | public enum ProgressOrResult { 9 | case progress(Progress) 10 | case result(Result) 11 | 12 | var progress: Progress? { 13 | switch self { 14 | case let .progress(task): 15 | return task 16 | case .result: 17 | return nil 18 | } 19 | } 20 | 21 | var result: Result? { 22 | switch self { 23 | case .progress: 24 | return nil 25 | case let .result(result): 26 | return result 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Sources/Networking/Models/ResponseStatus/ResponseStatus+ResponseType.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Foundation 4 | 5 | public extension ResponseStatus { 6 | var responseType: ResponseType { 7 | switch rawValue { 8 | case 100 ..< 200: 9 | return .informational 10 | 11 | case 200 ..< 300: 12 | return .success 13 | 14 | case 300 ..< 400: 15 | return .redirection 16 | 17 | case 400 ..< 500: 18 | return .clientError 19 | 20 | case 500 ..< 600: 21 | return .serverError 22 | 23 | default: 24 | return .undefined 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Sources/Networking/Models/ResponseType.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Foundation 4 | 5 | /// The response class representation of status codes, these get grouped by their first digit. 6 | public enum ResponseType { 7 | /// - informational: This class of status code indicates a provisional response, consisting only of the Status-Line and optional headers, and is terminated by an empty line. 8 | case informational 9 | 10 | /// - success: This class of status codes indicates the action requested by the client was received, understood, accepted, and processed successfully. 11 | case success 12 | 13 | /// - redirection: This class of status code indicates the client must take additional action to complete the request. 14 | case redirection 15 | 16 | /// - clientError: This class of status code is intended for situations in which the client seems to have erred. 17 | case clientError 18 | 19 | /// - serverError: This class of status code indicates the server failed to fulfill an apparently valid request. 20 | case serverError 21 | 22 | /// - undefined: The class of the status code cannot be resolved. 23 | case undefined 24 | } 25 | -------------------------------------------------------------------------------- /Sources/Networking/Services/Dumper/CodableNetworkingRequest.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Foundation 4 | 5 | public protocol NetworkingDumperProtocol { 6 | func dump(response: NetworkingResponse, requestUUID: UUID) 7 | } 8 | -------------------------------------------------------------------------------- /Sources/Networking/Services/Dumper/DumpEntry.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Foundation 4 | 5 | public extension NetworkingDumper { 6 | struct DumpEntry: Codable { 7 | public let request: CodableNetworkingRequest 8 | public let responseBody: String 9 | public let responseCode: Int 10 | public let loggerRequestUUID: UUID 11 | 12 | public init( 13 | response: NetworkingResponse, 14 | requestUUID: UUID 15 | ) { 16 | request = CodableNetworkingRequest(request: response.request) 17 | responseBody = String(data: response.data, encoding: .utf8) ?? "" 18 | responseCode = response.urlResponse.statusCode 19 | loggerRequestUUID = requestUUID 20 | } 21 | } 22 | } 23 | 24 | public extension NetworkingDumper { 25 | struct CodableNetworkingRequest: Codable, Equatable { 26 | public let baseURL: URL 27 | public let route: String 28 | public let method: HTTPMethod 29 | public let absoluteURL: URL 30 | public let body: String? 31 | public let timeout: TimeInterval? 32 | 33 | public init(request: NetworkingRequest) { 34 | baseURL = request.baseURL 35 | route = request.route 36 | absoluteURL = request.buildURLRequest(logger: nil).url?.absoluteURL ?? request.baseURL 37 | method = request.method 38 | body = request.body.flatMap { String(data: $0, encoding: .utf8) } 39 | timeout = request.timeout 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Sources/Networking/Services/NetworkingBasicAuthHeaderFactory.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Foundation 4 | import SwiftlaneCore 5 | 6 | public class NetworkingBasicAuthHeaderFactory: Encodable { 7 | public init() {} 8 | 9 | /// Encode `username` and `password` as Basic auth header. 10 | /// 11 | /// - Returns: `key` and `value` of auth header. 12 | /// 13 | /// `key` is always `"Authorization"`. 14 | /// 15 | /// `value` is `"Basic "` where `` is Base64 encoded `"username:password"` string. 16 | public func makeAuthHeader( 17 | username: String, 18 | password: String 19 | ) throws -> (key: String, value: String) { 20 | let base64CredentialsString = try [username, password].joined(separator: ":") 21 | .data(using: .utf8) 22 | .unwrap() 23 | .base64EncodedString() 24 | 25 | return ("Authorization", "Basic \(base64CredentialsString)") 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Sources/Networking/Services/NetworkingClient/NetworkingClientProtocol.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Foundation 4 | import SwiftlaneCore 5 | 6 | // sourcery: AutoMockable 7 | public protocol NetworkingClientProtocol: AnyObject { 8 | /// Headers to include in each request. 9 | /// > Some of them may be overriden with per-request headers in case of key conflict. 10 | var commonHeaders: [String: String] { get set } 11 | 12 | /// Default timeout for all requests. 13 | /// > May be overriden by timout specified for a specific request. 14 | var timeout: TimeInterval { get set } 15 | 16 | /// Called before performing request. You can implement custom behaviour to modify the request. 17 | var willPerformRequest: ((inout NetworkingRequest) -> Void)? { get set } 18 | 19 | /// Level of logs for logger. 20 | var logLevel: LoggingLevel { get set } 21 | 22 | /// Create builder for GET request. 23 | func get(_ route: String) -> NetworkingRequestBuilder 24 | 25 | /// Create builder for PUT request. 26 | func put(_ route: String) -> NetworkingRequestBuilder 27 | 28 | /// Create builder for PATCH request. 29 | func patch(_ route: String) -> NetworkingRequestBuilder 30 | 31 | /// Create builder for POST request. 32 | func post(_ route: String) -> NetworkingRequestBuilder 33 | 34 | /// Create builder for DELETE request. 35 | func delete(_ route: String) -> NetworkingRequestBuilder 36 | } 37 | -------------------------------------------------------------------------------- /Sources/Networking/Services/Serialization/DeserializerProtocol.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Foundation 4 | 5 | public protocol DeserializerProtocol { 6 | func deseriaize(_ type: T.Type, from data: Data) throws -> T 7 | } 8 | -------------------------------------------------------------------------------- /Sources/Networking/Services/Serialization/JSONDeserializer.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Foundation 4 | 5 | public final class JSONDeserializer: DeserializerProtocol { 6 | private let decoder: JSONDecoder 7 | 8 | public init(decoder: JSONDecoder) { 9 | self.decoder = decoder 10 | } 11 | 12 | public func deseriaize(_: T.Type, from data: Data) throws -> T { 13 | try decoder.decode(T.self, from: data) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Sources/Networking/Services/Serialization/JSONSerializer.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Foundation 4 | 5 | public final class JSONSerializer: SerializerProtocol { 6 | private let encoder: JSONEncoder 7 | 8 | public init(encoder: JSONEncoder) { 9 | self.encoder = encoder 10 | } 11 | 12 | public func serialize(_ object: T) throws -> Data { 13 | try encoder.encode(object) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Sources/Networking/Services/Serialization/SerializerProtocol.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Foundation 4 | 5 | public protocol SerializerProtocol { 6 | func serialize(_ object: T) throws -> Data 7 | } 8 | -------------------------------------------------------------------------------- /Sources/Provisioning/HighLevelServices/Match/CertificatesConstants.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Foundation 4 | 5 | public enum CodeSigningCertificateType: String { 6 | case distribution 7 | case development 8 | } 9 | 10 | public enum ProvisionProfileType: String { 11 | case appstore 12 | case adhoc 13 | case development 14 | } 15 | 16 | internal enum CertificatesConstants { 17 | static let certificateFileExtension = ".cer" 18 | static let privateKeyExtension = ".p12" 19 | } 20 | -------------------------------------------------------------------------------- /Sources/Provisioning/LowLevelServices/CertificateParser/CertificateParser.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Foundation 4 | import Security 5 | import SwiftlaneCore 6 | 7 | public struct Certificate { 8 | public let certificate: SecCertificate 9 | public let name: String? 10 | public let notValidBefore: Date? 11 | public let notValidAfter: Date? 12 | } 13 | 14 | public final class CertificateParser { 15 | private let filesManager: FSManaging 16 | 17 | public init( 18 | filesManager: FSManaging 19 | ) { 20 | self.filesManager = filesManager 21 | } 22 | 23 | public func parse(path _: AbsolutePath) throws -> Certificate { 24 | // let data = try filesManager.readData(path, log: true) 25 | // 26 | // let certificate = try SecCertificateCreateWithData(nil, data as NSData).unwrap( 27 | // errorDescription: "SecCertificateCreateWithData returned nil." 28 | // ) 29 | // 30 | // let values = SecCertificateCopyValues(certificate, nil, nil) as? [String: Any] 31 | // 32 | // let fingerprintsArrayContainer = values?["Fingerprints"] as? [String: Any] 33 | // let fingerprintsArray = try (fingerprintsArrayContainer?["value"] as? [[String: Any]]).unwrap() 34 | // 35 | // let _prints = Dictionary(uniqueKeysWithValues: fingerprintsArray.compactMap { 36 | // ($0["label"] as! String, $0["value"] as! Data) 37 | // }) 38 | // 39 | fatalError("Not implemented") 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Sources/Provisioning/LowLevelServices/MacOSSecurity/CodesigningIdentity.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Foundation 4 | 5 | public struct CodesigningIdentity: Codable { 6 | public let fingerprint: String 7 | public let type: String 8 | public let name: String 9 | public let teamID: String 10 | } 11 | -------------------------------------------------------------------------------- /Sources/Provisioning/LowLevelServices/MobileProvision/MobileProvision.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Foundation 4 | import SwiftlaneCore 5 | 6 | /// iOS Mobile Provisioning Profile data model. 7 | /// 8 | /// Can be parsed from `.mobileprovision` file (see `MobileProvisionParser`). 9 | /// 10 | /// Info: MacOS Provisioning Profile have different file extension `.provisionprofile`. 11 | /// MacOS Provisioning Profiles have slightly different data structure 12 | /// and they should be installed in a different way. 13 | public struct MobileProvision: Codable, Equatable { 14 | public let AppIDName: String 15 | public let ApplicationIdentifierPrefix: [String] 16 | public let CreationDate: Date 17 | public let Platform: [String] 18 | public let IsXcodeManaged: Bool 19 | public let ExpirationDate: Date 20 | public let Name: String 21 | public let TeamIdentifier: [String] 22 | public let Entitlements: MobileProvisionEntitlements 23 | public let TeamName: String 24 | public let TimeToLive: Int 25 | public let UUID: String 26 | public let Version: Int 27 | 28 | public let DeveloperCertificates: [Data] 29 | 30 | public var applicationBundleID: String { 31 | ApplicationIdentifierPrefix.reduce(into: Entitlements.applicationIdentifier) { 32 | $0.replaceOccurrences(of: $1 + ".", with: "") 33 | } 34 | } 35 | 36 | /// [String: VALUE] // where VALUE can be of type Bool/Array/String 37 | public struct MobileProvisionEntitlements: Codable, Equatable { 38 | public let applicationIdentifier: String 39 | 40 | enum CodingKeys: String, CodingKey { 41 | case applicationIdentifier = "application-identifier" 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Sources/Provisioning/LowLevelServices/OpenSSL/OpenSSLCertificateFormat.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Foundation 4 | 5 | public enum OpenSSLCertificateFormat: String { 6 | case der 7 | case pem 8 | } 9 | -------------------------------------------------------------------------------- /Sources/Provisioning/LowLevelServices/OpenSSL/OpenSSLMsgDigest.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Foundation 4 | 5 | /// The message digest to use. Possible values include md5 and sha1. This option also applies to CRLs. 6 | public enum OpenSSLMsgDigest: String { 7 | case gost_mac = "gost-mac" 8 | case streebog512 9 | case streebog256 10 | case md_gost94 11 | case md4 12 | case md5 13 | case md5_sha1 = "md5-sha1" 14 | case ripemd160 15 | case sha1 16 | case sha224 17 | case sha256 18 | case sha384 19 | case sha512 20 | case whirlpool 21 | 22 | /// Returns `"-" + rawValue` 23 | public var asCliOption: String { 24 | "-" + rawValue 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Sources/Simulator/Models/SimCtlDevice.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Foundation 4 | 5 | public struct SimCtlDevice: Decodable, Equatable { 6 | public let dataPath: String 7 | public let logPath: String 8 | public let udid: String 9 | public let isAvailable: Bool 10 | public let deviceTypeIdentifier: String? 11 | public let state: State 12 | public let name: String 13 | 14 | public enum State: String, Decodable { 15 | case unknown = "" 16 | case creating = "Creating" 17 | case booting = "Booting" 18 | case booted = "Booted" 19 | case shuttingDown = "ShuttingDown" 20 | case shutdown = "Shutdown" 21 | 22 | public init(from decoder: Decoder) throws { 23 | let container = try decoder.singleValueContainer() 24 | let string = try container.decode(String.self) 25 | self = State(rawValue: string) ?? .unknown 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Sources/Simulator/Models/SimCtlRuntime.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Foundation 4 | 5 | /// Simulator runtime info. 6 | public struct SimCtlRuntime: Decodable, Equatable { 7 | /// E.g. `"com.apple.CoreSimulator.SimRuntime.tvOS-13-5"`. 8 | public let identifier: String 9 | /// Name is `platform + " " + version` 10 | public let name: String 11 | /// E.g. `"tvOS"`. 12 | public let platform: String? 13 | /// E.g. `"13.5"`. 14 | public let version: String 15 | /// E.g. `"19K50"`. 16 | public let buildversion: String 17 | public let isAvailable: Bool 18 | } 19 | -------------------------------------------------------------------------------- /Sources/Simulator/Models/SimulatorError.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Foundation 4 | 5 | public enum SimulatorError: Error { 6 | case noRuntimesFound 7 | case noDevicesFound 8 | case nilShOutput 9 | } 10 | -------------------------------------------------------------------------------- /Sources/Swiftlane/Commands/AddJiraIssueComment/AddJiraIssueCommentCommand.swift: -------------------------------------------------------------------------------- 1 | 2 | import ArgumentParser 3 | import Foundation 4 | import SwiftlaneCore 5 | 6 | public protocol AddJiraIssueCommentCommandParamsAccessing { 7 | var sharedConfigOptions: SharedConfigOptions { get } 8 | var configPath: AbsolutePath { get } 9 | var text: String { get } 10 | } 11 | 12 | /// CLI command to add a comment to jira issue 13 | public struct AddJiraIssueCommentCommand: ParsableCommand, AddJiraIssueCommentCommandParamsAccessing { 14 | public static var configuration = CommandConfiguration( 15 | commandName: "add-jira-issue-comment", 16 | abstract: "Add a comment to Jira issue." 17 | ) 18 | 19 | @OptionGroup public var sharedConfigOptions: SharedConfigOptions 20 | 21 | @Option( 22 | name: [.customLong("config")], 23 | help: "Path to config.yml" 24 | ) 25 | public var configPath: AbsolutePath 26 | 27 | @Option(help: "Comment text.") 28 | public var text: String 29 | 30 | public init() {} 31 | 32 | public mutating func run() throws { 33 | AddJiraIssueCommentCommandRunner().run(self, sharedConfigOptions: sharedConfigOptions, commandConfigPath: configPath) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Sources/Swiftlane/Commands/AddJiraIssueComment/AddJiraIssueCommentCommandRunner.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Foundation 4 | import Guardian 5 | import JiraAPI 6 | import Simulator 7 | import SwiftlaneCore 8 | import Yams 9 | 10 | public struct AddJiraIssueCommentCommandConfig: Decodable { 11 | public let ignoredIssues: [StringMatcher] 12 | } 13 | 14 | public struct AddJiraIssueCommentCommandRunner: CommandRunnerProtocol { 15 | public func run( 16 | params: AddJiraIssueCommentCommandParamsAccessing, 17 | commandConfig: AddJiraIssueCommentCommandConfig, 18 | sharedConfig: SharedConfigData 19 | ) throws { 20 | let taskConfig = AddJiraIssueCommentTask.Config( 21 | text: params.text, 22 | ignoredIssues: commandConfig.ignoredIssues 23 | ) 24 | 25 | let task = TasksFactory.makeAddJiraIssueCommentTask(taskConfig: taskConfig) 26 | 27 | try task.run(sharedConfig: sharedConfig.values) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Sources/Swiftlane/Commands/BuildCommand/BuildAppCommand.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import ArgumentParser 4 | import Foundation 5 | import SwiftlaneCore 6 | 7 | public protocol BuildAppCommandParamsAccessing { 8 | var sharedConfigOptions: SharedConfigOptions { get } 9 | 10 | var scheme: String { get } 11 | var buildConfiguration: String { get } 12 | var buildForTesting: Bool { get } 13 | } 14 | 15 | public struct BuildAppCommand: ParsableCommand, BuildAppCommandParamsAccessing { 16 | public static var configuration = CommandConfiguration( 17 | commandName: "build", 18 | abstract: "Build scheme for generic iOS device." 19 | ) 20 | 21 | @OptionGroup public var sharedConfigOptions: SharedConfigOptions 22 | 23 | @Option(help: "Scheme to build.") 24 | public var scheme: String 25 | 26 | @Option(help: "Build configuration. Common ones are 'Debug' and 'Release'.") 27 | public var buildConfiguration: String 28 | 29 | @Option(help: "Build for testing or running.") 30 | public var buildForTesting: Bool 31 | 32 | public init() {} 33 | 34 | public mutating func run() throws { 35 | BuildAppCommandRunner().run(self, sharedConfigOptions: sharedConfigOptions) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Sources/Swiftlane/Commands/BuildCommand/BuildAppCommandRunner.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import AppStoreConnectAPI 4 | import AppStoreConnectJWT 5 | import Foundation 6 | import Provisioning 7 | import Simulator 8 | import SwiftlaneCore 9 | import Xcodebuild 10 | import Yams 11 | 12 | public class BuildAppCommandRunner: CommandRunnerProtocol { 13 | private func build( 14 | params: BuildAppCommandParamsAccessing, 15 | paths: PathsFactoring 16 | ) throws { 17 | let builderConfig = Builder.Config( 18 | project: paths.projectFile, 19 | scheme: params.scheme, 20 | derivedDataPath: paths.derivedDataDir, 21 | logsPath: paths.logsDir, 22 | configuration: params.buildConfiguration, 23 | xcodebuildFormatterCommand: paths.xcodebuildFormatterCommand 24 | ) 25 | let buildTask = TasksFactory.makeBuildAppTask( 26 | builderConfig: builderConfig, 27 | buildForTesting: params.buildForTesting, 28 | buildDestination: .genericIOSDevice 29 | ) 30 | try buildTask.run() 31 | } 32 | 33 | public func run( 34 | params: BuildAppCommandParamsAccessing, 35 | commandConfig _: Void, 36 | sharedConfig: SharedConfigData 37 | ) throws { 38 | try build(params: params, paths: sharedConfig.paths) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Sources/Swiftlane/Commands/BuildNumberCommand/BuildNumberCommand.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import ArgumentParser 4 | import Foundation 5 | import SwiftlaneCore 6 | 7 | public struct BuildNumberCommandOptions: ParsableArguments { 8 | public init() {} 9 | } 10 | 11 | public struct BuildNumberCommand: ParsableCommand { 12 | public static var configuration = CommandConfiguration( 13 | commandName: "build-number", 14 | abstract: "Work with build number of project targets.", 15 | subcommands: [ 16 | BuildNumberSetCommand.self, 17 | ] 18 | ) 19 | 20 | public init() {} 21 | } 22 | -------------------------------------------------------------------------------- /Sources/Swiftlane/Commands/BuildNumberCommand/BuildNumberSetCommand.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import ArgumentParser 4 | import Foundation 5 | import SwiftlaneCore 6 | 7 | public protocol BuildNumberSetCommandParamsAccessing { 8 | var options: BuildNumberCommandOptions { get } 9 | var sharedConfigOptions: SharedConfigOptions { get } 10 | 11 | var buildSettings: Bool { get } 12 | var infoPlist: Bool { get } 13 | var buildNumber: String { get } 14 | } 15 | 16 | // swiftformat:disable indent 17 | public struct BuildNumberSetCommand: ParsableCommand, BuildNumberSetCommandParamsAccessing { 18 | public static var configuration = CommandConfiguration( 19 | commandName: "set", 20 | abstract: "Set provided build number." 21 | ) 22 | 23 | @OptionGroup public var options: BuildNumberCommandOptions 24 | 25 | @OptionGroup public var sharedConfigOptions: SharedConfigOptions 26 | 27 | @Flag(help: "Set provided build number value to CFBundleVersion in Info.plist files.") 28 | public var buildSettings: Bool = false 29 | 30 | @Flag(help: "Set provided build number value to CURRENT_PROJECT_VERSION in build setting.") 31 | public var infoPlist: Bool = false 32 | 33 | @Argument(help: "Build Number to set.") 34 | public var buildNumber: String 35 | 36 | public init() {} 37 | 38 | public func validate() throws { 39 | guard infoPlist || buildSettings else { 40 | throw ValidationError("Missing one of required options: --info-plist or --buildSettings.") 41 | } 42 | } 43 | 44 | public mutating func run() throws { 45 | BuildNumberSetCommandRunner().run(self, sharedConfigOptions: sharedConfigOptions) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Sources/Swiftlane/Commands/BuildNumberCommand/BuildNumberSetCommandRunner.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Foundation 4 | import SwiftlaneCore 5 | import Xcodebuild 6 | 7 | public class BuildNumberSetCommandRunner: CommandRunnerProtocol { 8 | public func run( 9 | params: BuildNumberSetCommandParamsAccessing, 10 | commandConfig _: Void, 11 | sharedConfig: SharedConfigData 12 | ) throws { 13 | let projectPatcher: XcodeProjectPatching = DependenciesFactory.resolve() 14 | 15 | if params.buildSettings { 16 | try projectPatcher.setCurrentProjectVersion( 17 | xcodeprojPath: sharedConfig.paths.projectFile, 18 | value: params.buildNumber 19 | ) 20 | } 21 | 22 | if params.infoPlist { 23 | try projectPatcher.setCFBundleVersion( 24 | xcodeprojPath: sharedConfig.paths.projectFile, 25 | value: params.buildNumber 26 | ) 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Sources/Swiftlane/Commands/CertsCommands/CertsCommand.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import ArgumentParser 4 | import Foundation 5 | import SwiftlaneCore 6 | 7 | public struct CertsCommandOptions: ParsableArguments { 8 | @Option(help: "Path to certificates repo config.") 9 | public var config: AbsolutePath = try! .init( 10 | FileManager.default.currentDirectoryPath 11 | .appendingPathComponent("certs-config.yml") 12 | ) 13 | 14 | @Option(help: "Where to temporary store cloned certificates repo.") 15 | public var clonedRepoDir: AbsolutePath 16 | 17 | @Option(help: "Password used to decrypt certificates repo.") 18 | public var repoPassword: SensitiveData? 19 | 20 | public init() {} 21 | } 22 | 23 | public struct CertsCommand: ParsableCommand { 24 | public static var configuration = CommandConfiguration( 25 | commandName: "certs", 26 | abstract: "Install certificates and provisioning profiles.", 27 | subcommands: [ 28 | CertsInstallCommand.self, 29 | CertsUpdateCommand.self, 30 | CertsChangePasswordCommand.self, 31 | ], 32 | defaultSubcommand: CertsInstallCommand.self 33 | ) 34 | 35 | public init() {} 36 | } 37 | -------------------------------------------------------------------------------- /Sources/Swiftlane/Commands/ChangeJiraIssueLabelsCommand/ChangeJiraIssueLabelsCommand.swift: -------------------------------------------------------------------------------- 1 | 2 | import ArgumentParser 3 | import Foundation 4 | import SwiftlaneCore 5 | 6 | public protocol ChangeJiraIssueLabelsCommandParamsAccessing { 7 | var sharedConfigOptions: SharedConfigOptions { get } 8 | var configPath: AbsolutePath { get } 9 | var neededLabels: [String] { get } 10 | var appendLabels: Bool { get } 11 | } 12 | 13 | /// CLI command to change status in jira issue 14 | public struct ChangeJiraIssueLabelsCommand: ParsableCommand, ChangeJiraIssueLabelsCommandParamsAccessing { 15 | public static var configuration = CommandConfiguration( 16 | commandName: "change-jira-issue-labels", 17 | abstract: "Starts an attempt to change the JIRA task labels" 18 | ) 19 | 20 | @OptionGroup public var sharedConfigOptions: SharedConfigOptions 21 | 22 | @Option( 23 | name: [.customLong("config")], 24 | help: "Path to config.yml" 25 | ) 26 | public var configPath: AbsolutePath 27 | 28 | @Option(help: "Target labels to set JIRA issue into") 29 | public var neededLabels: [String] 30 | 31 | @Flag(help: "Append or replacement labels. Default false - this means replacing") 32 | public var appendLabels: Bool = false 33 | 34 | public init() {} 35 | 36 | public mutating func run() throws { 37 | ChangeJiraIssueLabelsCommandRunner().run( 38 | self, 39 | sharedConfigOptions: sharedConfigOptions, 40 | commandConfigPath: configPath 41 | ) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Sources/Swiftlane/Commands/ChangeJiraIssueLabelsCommand/ChangeJiraIssueLabelsCommandRunner.swift: -------------------------------------------------------------------------------- 1 | 2 | import Foundation 3 | import Guardian 4 | import JiraAPI 5 | import Simulator 6 | import SwiftlaneCore 7 | import Yams 8 | 9 | public struct ChangeJiraIssueLabelsCommandConfig: Decodable {} 10 | 11 | public struct ChangeJiraIssueLabelsCommandRunner: CommandRunnerProtocol { 12 | public func run( 13 | params: ChangeJiraIssueLabelsCommandParamsAccessing, 14 | commandConfig _: ChangeJiraIssueLabelsCommandConfig, 15 | sharedConfig: SharedConfigData 16 | ) throws { 17 | let taskConfig = ChangeJiraIssueLabelsTask.Config( 18 | neededLabels: params.neededLabels, 19 | appendLabels: params.appendLabels 20 | ) 21 | 22 | let task = try TasksFactory.makeChangeJiraIssueLabelsTask(taskConfig: taskConfig) 23 | 24 | try task.run(sharedConfig: sharedConfig.values) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Sources/Swiftlane/Commands/ChangeVersionCommand/ChangeVersionCommand.swift: -------------------------------------------------------------------------------- 1 | 2 | import ArgumentParser 3 | import Foundation 4 | import SwiftlaneCore 5 | 6 | public protocol ChangeVersionCommandParamsAccessing { 7 | var sharedConfigOptions: SharedConfigOptions { get } 8 | var action: ChangeVersionCommand.Action { get } 9 | } 10 | 11 | public struct ChangeVersionCommand: ParsableCommand, ChangeVersionCommandParamsAccessing { 12 | public static var configuration = CommandConfiguration( 13 | commandName: "change-version", 14 | abstract: "Change current project version." 15 | ) 16 | 17 | public enum Action: String, EnumerableFlag { 18 | case bumpMajor 19 | case bumpMinor 20 | case bumpPatch 21 | } 22 | 23 | @OptionGroup public var sharedConfigOptions: SharedConfigOptions 24 | 25 | @Flag(help: "What to do.") 26 | public var action: Action 27 | 28 | @Option( 29 | name: [.customLong("config")], 30 | help: "Path to config.yml" 31 | ) 32 | public var configPath: AbsolutePath 33 | 34 | public init() {} 35 | 36 | public mutating func run() throws { 37 | ChangeVersionCommandRunner() 38 | .run(self, sharedConfigOptions: sharedConfigOptions, commandConfigPath: configPath) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Sources/Swiftlane/Commands/CheckCommitsCommand/CheckCommitsCommand.swift: -------------------------------------------------------------------------------- 1 | 2 | import ArgumentParser 3 | import Foundation 4 | import SwiftlaneCore 5 | 6 | public protocol CheckCommitsCommandParamsAccessing { 7 | var sharedConfigOptions: SharedConfigOptions { get } 8 | var configPath: AbsolutePath { get } 9 | } 10 | 11 | /// CLI command to change status in jira issue 12 | public struct CheckCommitsCommand: ParsableCommand, CheckCommitsCommandParamsAccessing { 13 | public static var configuration = CommandConfiguration( 14 | commandName: "check-commits", 15 | abstract: "Checks for important commits on the HEAD" 16 | ) 17 | 18 | @OptionGroup public var sharedConfigOptions: SharedConfigOptions 19 | 20 | @Option( 21 | name: [.customLong("config")], 22 | help: "Path to config.yml" 23 | ) 24 | public var configPath: AbsolutePath 25 | 26 | public init() {} 27 | 28 | public mutating func run() throws { 29 | CheckCommitsCommandRunner().run(self, sharedConfigOptions: sharedConfigOptions, commandConfigPath: configPath) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Sources/Swiftlane/Commands/CheckCommitsCommand/CheckCommitsCommandRunner.swift: -------------------------------------------------------------------------------- 1 | 2 | import Foundation 3 | import Git 4 | import GitLabAPI 5 | import Guardian 6 | import Simulator 7 | import SwiftlaneCore 8 | import Yams 9 | 10 | public struct CheckCommitsCommandConfig: Decodable { 11 | public struct CommitsForCheck: Decodable { 12 | public let name: StringMatcher 13 | public let commits: [String] 14 | } 15 | 16 | public let commitsForCheck: [CommitsForCheck] 17 | } 18 | 19 | public struct CheckCommitsCommandRunner: CommandRunnerProtocol { 20 | public func run( 21 | params: CheckCommitsCommandParamsAccessing, 22 | commandConfig: CheckCommitsCommandConfig, 23 | sharedConfig _: SharedConfigData 24 | ) throws { 25 | let checkerConfig = CommitsChecker.Config( 26 | projectDir: params.sharedConfigOptions.projectDir, 27 | commitsForCheck: commandConfig.commitsForCheck.map { .init(name: $0.name, commits: $0.commits) } 28 | ) 29 | 30 | let task = try TasksFactory.makeCheckCommitsTask(checkerConfig: checkerConfig) 31 | 32 | try task.run() 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Sources/Swiftlane/Commands/CheckStopListCommand/CheckStopListCommand.swift: -------------------------------------------------------------------------------- 1 | 2 | import ArgumentParser 3 | import Foundation 4 | import SwiftlaneCore 5 | 6 | public protocol CheckStopListCommandParamsAccessing { 7 | var sharedConfigOptions: SharedConfigOptions { get } 8 | var configPath: AbsolutePath { get } 9 | } 10 | 11 | /// CLI command to change status in jira issue 12 | public struct CheckStopListCommand: ParsableCommand, CheckStopListCommandParamsAccessing { 13 | public static var configuration = CommandConfiguration( 14 | commandName: "check-stop-list", 15 | abstract: "Checks for changes in files located in the `stop list`" 16 | ) 17 | 18 | @OptionGroup public var sharedConfigOptions: SharedConfigOptions 19 | 20 | @Option( 21 | name: [.customLong("config")], 22 | help: "Path to config.yml" 23 | ) 24 | public var configPath: AbsolutePath 25 | 26 | public init() {} 27 | 28 | public mutating func run() throws { 29 | CheckStopListCommandRunner().run(self, sharedConfigOptions: sharedConfigOptions, commandConfigPath: configPath) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Sources/Swiftlane/Commands/CheckStopListCommand/CheckStopListCommandRunner.swift: -------------------------------------------------------------------------------- 1 | 2 | import Foundation 3 | import Git 4 | import GitLabAPI 5 | import Guardian 6 | import Simulator 7 | import SwiftlaneCore 8 | import Yams 9 | 10 | public struct CheckStopListCommandConfig: Decodable { 11 | public let stopListConfigPath: String 12 | public let excludingUsers: [StringMatcher] 13 | } 14 | 15 | public struct CheckStopListCommandRunner: CommandRunnerProtocol { 16 | public func run( 17 | params: CheckStopListCommandParamsAccessing, 18 | commandConfig: CheckStopListCommandConfig, 19 | sharedConfig _: SharedConfigData 20 | ) throws { 21 | let environmentValueReader: EnvironmentValueReading = DependenciesFactory.resolve() 22 | let filesManager: FSManaging = DependenciesFactory.resolve() 23 | 24 | let expandedStopListConfigPath = try environmentValueReader.expandVariables( 25 | in: commandConfig.stopListConfigPath 26 | ) 27 | 28 | let stopListConfig: CheckStopListTask.StopListConfig = try filesManager.decode( 29 | try AbsolutePath(expandedStopListConfigPath), 30 | decoder: YAMLDecoder() 31 | ) 32 | 33 | let taskConfig = CheckStopListTask.Config( 34 | stopListConfig: stopListConfig 35 | ) 36 | 37 | let task = try TasksFactory.makeCheckStopListTask( 38 | taskConfig: taskConfig 39 | ) 40 | 41 | try task.run( 42 | projectDir: params.sharedConfigOptions.projectDir 43 | ) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Sources/Swiftlane/Commands/GuardianAfterBuildCommand/GuardianAfterBuildCommand.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import ArgumentParser 4 | import Foundation 5 | import Guardian 6 | import SwiftlaneCore 7 | import Yams 8 | 9 | public protocol GuardianAfterBuildCommandParamsAccessing { 10 | var sharedConfigOptions: SharedConfigOptions { get } 11 | var configPath: AbsolutePath { get } 12 | var unitTestsExitCode: Int { get } 13 | } 14 | 15 | /// CLI command to run Guardian After build. 16 | public struct GuardianAfterBuildCommand: ParsableCommand, GuardianAfterBuildCommandParamsAccessing { 17 | public static var configuration = CommandConfiguration( 18 | commandName: "guardian-after-build", 19 | abstract: "Report any occured errors during building or running tests and validate code coverage limits." 20 | ) 21 | 22 | @OptionGroup public var sharedConfigOptions: SharedConfigOptions 23 | 24 | @Option( 25 | name: [.customLong("config")], 26 | help: "Path to config.yml" 27 | ) 28 | public var configPath: AbsolutePath 29 | 30 | @Option(help: "Unit tests exit code") 31 | public var unitTestsExitCode: Int 32 | 33 | public init() {} 34 | 35 | public mutating func run() throws { 36 | GuardianAfterBuildCommandRunner().run( 37 | self, 38 | sharedConfigOptions: sharedConfigOptions, 39 | commandConfigPath: configPath 40 | ) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Sources/Swiftlane/Commands/GuardianAfterBuildCommand/GuardianAfterBuildCommandConfig.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Foundation 4 | import SwiftlaneCore 5 | 6 | public struct GuardianAfterBuildCommandConfig: Decodable { 7 | public let changesCoverageLimitCheckerConfig: ChangesCoverageLimitChecker.DecodableConfig? 8 | public let targetsCoverageLimitCheckerConfig: TargetsCoverageLimitChecker.DecodableConfig 9 | public let buildWarningCheckerConfig: BuildWarningsChecker.DecodableConfig 10 | public let slatherReportFilePath: Path 11 | 12 | public init( 13 | changesCoverageLimitCheckerConfig: ChangesCoverageLimitChecker.DecodableConfig?, 14 | targetsCoverageLimitCheckerConfig: TargetsCoverageLimitChecker.DecodableConfig, 15 | buildWarningCheckerConfig: BuildWarningsChecker.DecodableConfig, 16 | slatherReportFilePath: Path 17 | ) { 18 | self.changesCoverageLimitCheckerConfig = changesCoverageLimitCheckerConfig 19 | self.targetsCoverageLimitCheckerConfig = targetsCoverageLimitCheckerConfig 20 | self.buildWarningCheckerConfig = buildWarningCheckerConfig 21 | self.slatherReportFilePath = slatherReportFilePath 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Sources/Swiftlane/Commands/GuardianAfterBuildCommand/GuardianAfterBuildCommandRunner.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Foundation 4 | import Git 5 | import GitLabAPI 6 | import Guardian 7 | import SwiftlaneCore 8 | import Yams 9 | 10 | public struct GuardianAfterBuildCommandRunner: CommandRunnerProtocol { 11 | public func run( 12 | params: GuardianAfterBuildCommandParamsAccessing, 13 | commandConfig: GuardianAfterBuildCommandConfig, 14 | sharedConfig: SharedConfigData 15 | ) throws { 16 | let task = try TasksFactory.makeGuardianAfterBuildTask( 17 | projectDir: params.sharedConfigOptions.projectDir, 18 | commandConfig: commandConfig, 19 | sharedConfig: sharedConfig, 20 | unitTestsExitCode: params.unitTestsExitCode 21 | ) 22 | 23 | try task.run() 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Sources/Swiftlane/Commands/GuardianBeforeBuildCommand/GuardianBeforeBuildCommand.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import ArgumentParser 4 | import Foundation 5 | import SwiftlaneCore 6 | 7 | public protocol GuardianBeforeBuildCommandParamsAccessing { 8 | var sharedConfigOptions: SharedConfigOptions { get } 9 | var configPath: AbsolutePath { get } 10 | } 11 | 12 | /// CLI command to run Guardian after build. 13 | public struct GuardianBeforeBuildCommand: ParsableCommand, GuardianBeforeBuildCommandParamsAccessing { 14 | public static var configuration = CommandConfiguration( 15 | commandName: "guardian-before-build", 16 | abstract: "Run varoius checks before build such as warning limits, indentation using tabs etc." 17 | ) 18 | 19 | @OptionGroup public var sharedConfigOptions: SharedConfigOptions 20 | 21 | @Option( 22 | name: [.customLong("config")], 23 | help: "Path to config.yml" 24 | ) 25 | public var configPath: AbsolutePath 26 | 27 | public init() {} 28 | 29 | public mutating func run() throws { 30 | GuardianBeforeBuildCommandRunner().run( 31 | self, 32 | sharedConfigOptions: sharedConfigOptions, 33 | commandConfigPath: configPath 34 | ) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Sources/Swiftlane/Commands/GuardianBeforeBuildCommand/GuardianBeforeBuildCommandRunner.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Foundation 4 | import Git 5 | import GitLabAPI 6 | import Guardian 7 | import SwiftlaneCore 8 | import Yams 9 | 10 | public struct GuardianBeforeBuildCommandRunner: CommandRunnerProtocol { 11 | public func run( 12 | params: GuardianBeforeBuildCommandParamsAccessing, 13 | commandConfig: GuardianBeforeBuildCommandConfig, 14 | sharedConfig: SharedConfigData 15 | ) throws { 16 | let task = try TasksFactory.makeGuardianBeforeBuildTask( 17 | projectDir: params.sharedConfigOptions.projectDir, 18 | commandConfig: commandConfig, 19 | sharedConfig: sharedConfig 20 | ) 21 | 22 | try task.run() 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Sources/Swiftlane/Commands/GuardianCheckAuthorCommand/GuardianCheckAuthorCommand.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import ArgumentParser 4 | import Foundation 5 | import SwiftlaneCore 6 | 7 | public protocol GuardianCheckAuthorCommandParamsAccessing { 8 | var sharedConfigOptions: SharedConfigOptions { get } 9 | } 10 | 11 | /// CLI command to run Guardian after build. 12 | public struct GuardianCheckAuthorCommand: ParsableCommand, GuardianCheckAuthorCommandParamsAccessing { 13 | public static var configuration = CommandConfiguration( 14 | commandName: "guardian-check-author", 15 | abstract: "Check that author has set a custom avatar and their name is correct. Also checks author's name in MR commits." 16 | ) 17 | 18 | @OptionGroup public var sharedConfigOptions: SharedConfigOptions 19 | 20 | @Option(help: "Path to config.yml of this command.") 21 | public var config: AbsolutePath 22 | 23 | public init() {} 24 | 25 | public mutating func run() throws { 26 | GuardianCheckAuthorCommandRunner().run(self, sharedConfigOptions: sharedConfigOptions, commandConfigPath: config) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Sources/Swiftlane/Commands/GuardianCheckAuthorCommand/GuardianCheckAuthorCommandRunner.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Foundation 4 | import Git 5 | import GitLabAPI 6 | import Guardian 7 | import SwiftlaneCore 8 | 9 | public struct GuardianCheckAuthorCommandConfig: Decodable { 10 | public let validGitLabUserName: DescriptiveStringMatcher 11 | public let validCommitAuthorName: DescriptiveStringMatcher 12 | } 13 | 14 | public struct GuardianCheckAuthorCommandRunner: CommandRunnerProtocol { 15 | public func run( 16 | params _: GuardianCheckAuthorCommandParamsAccessing, 17 | commandConfig: GuardianCheckAuthorCommandConfig, 18 | sharedConfig _: SharedConfigData 19 | ) throws { 20 | let task = try TasksFactory.makeGuardianCheckAuthorTask(commandConfig: commandConfig) 21 | 22 | try task.run() 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Sources/Swiftlane/Commands/GuardianInitialNoteCommand/GuardianInitialNoteCommand.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import ArgumentParser 4 | import Foundation 5 | 6 | public protocol GuardianInitialNoteCommandParamsAccessing { 7 | var sharedConfigOptions: SharedConfigOptions { get } 8 | } 9 | 10 | public struct GuardianInitialNoteCommand: ParsableCommand, GuardianInitialNoteCommandParamsAccessing { 11 | public static var configuration = CommandConfiguration( 12 | commandName: "guardian-initial-note", 13 | abstract: "Run Guardian to take the first place in Merge Request comments." 14 | ) 15 | 16 | @OptionGroup public var sharedConfigOptions: SharedConfigOptions 17 | 18 | public init() {} 19 | 20 | public mutating func run() throws { 21 | GuardianInitialNoteCommandRunner().run(self, sharedConfigOptions: sharedConfigOptions) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Sources/Swiftlane/Commands/GuardianInitialNoteCommand/GuardianInitialNoteCommandRunner.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Foundation 4 | import Git 5 | import GitLabAPI 6 | import Guardian 7 | import SwiftlaneCore 8 | 9 | public struct GuardianInitialNoteCommandRunner: CommandRunnerProtocol { 10 | public func run( 11 | params _: GuardianInitialNoteCommandParamsAccessing, 12 | commandConfig _: Void, 13 | sharedConfig _: SharedConfigData 14 | ) throws { 15 | let task = try TasksFactory.makeGuardianInitialNoteTask() 16 | 17 | try task.run() 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Sources/Swiftlane/Commands/MeasureBuildTimeCommand/MeasureBuildTimeCommandRunner.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Foundation 4 | import Simulator 5 | import SwiftlaneCore 6 | import Xcodebuild 7 | import Yams 8 | 9 | public class MeasureBuildTimeCommandRunner: CommandRunnerProtocol { 10 | public func run( 11 | params: MeasureBuildTimeCommandParamsAccessing, 12 | commandConfig _: Void, 13 | sharedConfig: SharedConfigData 14 | ) throws { 15 | let builderConfig = Builder.Config( 16 | project: sharedConfig.paths.projectFile, 17 | scheme: params.scheme, 18 | derivedDataPath: sharedConfig.paths.derivedDataDir, 19 | logsPath: sharedConfig.paths.logsDir, 20 | configuration: params.configuration, 21 | xcodebuildFormatterCommand: sharedConfig.paths.xcodebuildFormatterCommand 22 | ) 23 | 24 | let config = MeasureBuildTimeTask.Config( 25 | deviceModel: params.deviceModel, 26 | osVersion: params.osVersion, 27 | iterations: params.iterations, 28 | buildForTesting: params.buildForTesting 29 | ) 30 | 31 | let task = try TasksFactory.makeMeasureBuildTimeTask(builderConfig: builderConfig, config: config) 32 | 33 | try task.run() 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Sources/Swiftlane/Commands/PatchTestPlanEnvCommand/PatchTestPlanEnvCommand.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import ArgumentParser 4 | import Foundation 5 | import SwiftlaneCore 6 | 7 | public protocol PatchTestPlanEnvCommandParamsAccessing { 8 | var sharedConfigOptions: SharedConfigOptions { get } 9 | var configPath: AbsolutePath { get } 10 | var testPlanName: String { get } 11 | } 12 | 13 | public struct PatchTestPlanEnvCommand: ParsableCommand, PatchTestPlanEnvCommandParamsAccessing { 14 | public static var configuration = CommandConfiguration( 15 | commandName: "patch-test-plan-env", 16 | abstract: "Patches environment variables in test plan according to current proccess environment." 17 | ) 18 | 19 | @OptionGroup public var sharedConfigOptions: SharedConfigOptions 20 | 21 | @Option( 22 | name: [.customLong("config")], 23 | help: "Path to config.yml" 24 | ) 25 | public var configPath: AbsolutePath 26 | 27 | @Option(help: "Test plan name without public extension (e.g. 'BaseTestPlan')") 28 | public var testPlanName: String 29 | 30 | public init() {} 31 | 32 | public mutating func run() throws { 33 | PatchTestPlanEnvCommandRunner().run(self, sharedConfigOptions: sharedConfigOptions, commandConfigPath: configPath) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Sources/Swiftlane/Commands/PatchTestPlanEnvCommand/PatchTestPlanEnvCommandRunner.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Guardian 3 | import Simulator 4 | import SwiftlaneCore 5 | import Xcodebuild 6 | import Yams 7 | 8 | public struct PatchTestPlanEnvCommandConfig: Decodable { 9 | public let patchVariablesWithNames: [StringMatcher] 10 | public let testPlanSearchExcludedDirs: [RelativePath]? 11 | public let testPlanSearchIncludedDirs: [RelativePath] 12 | } 13 | 14 | public struct PatchTestPlanEnvCommandRunner: CommandRunnerProtocol { 15 | public func run( 16 | params: PatchTestPlanEnvCommandParamsAccessing, 17 | commandConfig: PatchTestPlanEnvCommandConfig, 18 | sharedConfig _: SharedConfigData 19 | ) throws { 20 | let config = PatchTestPlanEnvTaskConfig( 21 | testPlanName: params.testPlanName, 22 | projectDir: params.sharedConfigOptions.projectDir, 23 | patchVariablesWithNames: commandConfig.patchVariablesWithNames, 24 | testPlanSearchExcludedDirs: commandConfig.testPlanSearchExcludedDirs?.map { 25 | params.sharedConfigOptions.projectDir.appending(path: $0) 26 | } ?? [], 27 | testPlanSearchIncludedDirs: commandConfig.testPlanSearchIncludedDirs.map { 28 | params.sharedConfigOptions.projectDir.appending(path: $0) 29 | } 30 | ) 31 | 32 | let task = try TasksFactory.makePatchTestPlanEnvTask(config: config) 33 | 34 | try task.run() 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Sources/Swiftlane/Commands/PingCommand/PingCommand.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import ArgumentParser 4 | import Foundation 5 | import SwiftlaneCore 6 | 7 | public struct PingCommand: ParsableCommand { 8 | public static var configuration = CommandConfiguration( 9 | commandName: "ping", 10 | abstract: "Useless command." 11 | ) 12 | 13 | @OptionGroup public var commonOptions: CommonOptions 14 | 15 | public init() {} 16 | 17 | public func run() throws { 18 | print("[ping command] hi there!") 19 | 20 | Runner().run(self, commonOptions: commonOptions) 21 | } 22 | } 23 | 24 | private class Runner: CommandRunnerProtocol { 25 | public func run( 26 | params _: PingCommand, 27 | commandConfig _: Void, 28 | sharedConfig _: Void 29 | ) throws { 30 | let logger: Logging = DependenciesFactory.resolve() 31 | 32 | logger.error("logger.error") 33 | logger.warn("logger.warn") 34 | logger.important("logger.important") 35 | logger.info("logger.log") 36 | logger.debug("logger.debug") 37 | logger.verbose("logger.verbose") 38 | 39 | logger.logShellCommand("logger.logShellCommand somearg") 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Sources/Swiftlane/Commands/ReportUnusedCodeCommand/ReportUnusedCodeCommandRunner.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Foundation 4 | import Git 5 | import Guardian 6 | import MattermostAPI 7 | import Provisioning 8 | import Simulator 9 | import SwiftlaneCore 10 | import Xcodebuild 11 | import Yams 12 | 13 | public class ReportUnusedCodeCommandRunner: CommandRunnerProtocol { 14 | public func run( 15 | params: ReportUnusedCodeCommandParamsAccessing, 16 | commandConfig _: Void, 17 | sharedConfig: SharedConfigData 18 | ) throws { 19 | let taskConfig = ReportUnusedCodeTaskConfig( 20 | projectDir: params.sharedConfigOptions.projectDir, 21 | reportedFiles: params.reportedFile, 22 | ignoredTypesNames: params.ignoreTypeName, 23 | mattermostWebhookKey: params.mattermostWebhookKey, 24 | buildUsingPeriphery: params.build 25 | ) 26 | 27 | let task = try TasksFactory.makeReportUnusedCodeTask( 28 | taskConfig: taskConfig, 29 | mattermostApiURL: params.mattermostApiURL, 30 | paths: sharedConfig.paths 31 | ) 32 | 33 | try task.run() 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Sources/Swiftlane/Commands/SetProvisioningCommand/SetProvisioningCommand.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import ArgumentParser 4 | import Foundation 5 | import SwiftlaneCore 6 | 7 | public protocol SetProvisioningCommandParamsAccessing { 8 | var sharedConfigOptions: SharedConfigOptions { get } 9 | 10 | var scheme: String { get } 11 | var buildConfiguration: String { get } 12 | var provisionProfileName: String { get } 13 | } 14 | 15 | public struct SetProvisioningCommand: ParsableCommand, SetProvisioningCommandParamsAccessing { 16 | public static var configuration = CommandConfiguration( 17 | commandName: "set-provisioning-profile", 18 | abstract: "Set provisioning profile for a target based on scheme and build configuration." 19 | ) 20 | 21 | @OptionGroup public var sharedConfigOptions: SharedConfigOptions 22 | 23 | @Option(help: "Scheme of target to set provisioning profile for.") 24 | public var scheme: String 25 | 26 | @Option(help: "Build configuration. Common ones are 'Debug' and 'Release'.") 27 | public var buildConfiguration: String 28 | 29 | @Option( 30 | help: "Provisioning profile name. The profile should be installed in '~/Library/MobileDevice/Provisioning Profiles/'." 31 | ) 32 | public var provisionProfileName: String 33 | 34 | public init() {} 35 | 36 | public mutating func run() throws { 37 | SetProvisioningCommandRunner().run(self, sharedConfigOptions: sharedConfigOptions) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Sources/Swiftlane/Commands/SetProvisioningCommand/SetProvisioningCommandRunner.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import AppStoreConnectAPI 4 | import AppStoreConnectJWT 5 | import Foundation 6 | import Provisioning 7 | import Simulator 8 | import SwiftlaneCore 9 | import Xcodebuild 10 | import Yams 11 | 12 | public class SetProvisioningCommandRunner: CommandRunnerProtocol { 13 | private func updateProvisioning( 14 | params: SetProvisioningCommandParamsAccessing, 15 | paths: PathsFactoring 16 | ) throws { 17 | let taskConfig = SetProvisioningTaskConfig( 18 | xcodeprojPath: paths.projectFile, 19 | schemeName: params.scheme, 20 | buildConfigurationName: params.buildConfiguration, 21 | provisionProfileName: params.provisionProfileName 22 | ) 23 | let updateProvisioningTask = TasksFactory.makeSetProvisioningTask( 24 | taskConfig: taskConfig 25 | ) 26 | try updateProvisioningTask.run() 27 | } 28 | 29 | public func run( 30 | params: SetProvisioningCommandParamsAccessing, 31 | commandConfig _: Void, 32 | sharedConfig: SharedConfigData 33 | ) throws { 34 | try updateProvisioning(params: params, paths: sharedConfig.paths) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Sources/Swiftlane/Commands/SetupAssignesCommand/SetupAssigneeCommand.swift: -------------------------------------------------------------------------------- 1 | 2 | import ArgumentParser 3 | import Foundation 4 | import SwiftlaneCore 5 | 6 | public protocol SetupAssigneeCommandParamsAccessing { 7 | var sharedConfigOptions: SharedConfigOptions { get } 8 | var configPath: AbsolutePath { get } 9 | } 10 | 11 | /// CLI command to change status in jira issue 12 | public struct SetupAssigneeCommand: ParsableCommand, SetupAssigneeCommandParamsAccessing { 13 | public static var configuration = CommandConfiguration( 14 | commandName: "setup-assignee", 15 | abstract: "Added assignee by author in Merge Request" 16 | ) 17 | 18 | @OptionGroup public var sharedConfigOptions: SharedConfigOptions 19 | 20 | @Option( 21 | name: [.customLong("config")], 22 | help: "Path to config.yml" 23 | ) 24 | public var configPath: AbsolutePath 25 | 26 | public init() {} 27 | 28 | public mutating func run() throws { 29 | SetupAssigneeCommandRunner().run(self, sharedConfigOptions: sharedConfigOptions, commandConfigPath: configPath) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Sources/Swiftlane/Commands/SetupAssignesCommand/SetupAssigneeCommandRunner.swift: -------------------------------------------------------------------------------- 1 | 2 | import Foundation 3 | import GitLabAPI 4 | import Guardian 5 | import Simulator 6 | import SwiftlaneCore 7 | import Yams 8 | 9 | public struct SetupAssigneeCommandConfig: Decodable {} 10 | 11 | public struct SetupAssigneeCommandRunner: CommandRunnerProtocol { 12 | public func run( 13 | params _: SetupAssigneeCommandParamsAccessing, 14 | commandConfig _: SetupAssigneeCommandConfig, 15 | sharedConfig: SharedConfigData 16 | ) throws { 17 | let taskConfig = SetupAssigneeTask.Config() 18 | 19 | let task = SetupAssigneeTask( 20 | logger: DependenciesFactory.resolve(), 21 | config: taskConfig, 22 | gitlabCIEnvironment: DependenciesFactory.resolve(), 23 | gitlabApi: DependenciesFactory.resolve() 24 | ) 25 | 26 | try task.run() 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Sources/Swiftlane/Commands/SetupLabelsCommand/SetupLabelsCommand.swift: -------------------------------------------------------------------------------- 1 | 2 | import ArgumentParser 3 | import Foundation 4 | import SwiftlaneCore 5 | 6 | public protocol SetupLabelsCommandParamsAccessing { 7 | var sharedConfigOptions: SharedConfigOptions { get } 8 | var configPath: AbsolutePath { get } 9 | } 10 | 11 | /// CLI command to change status in jira issue 12 | public struct SetupLabelsCommand: ParsableCommand, SetupLabelsCommandParamsAccessing { 13 | public static var configuration = CommandConfiguration( 14 | commandName: "setup-labels", 15 | abstract: "Parse config and update labels in Merge Request" 16 | ) 17 | 18 | @OptionGroup public var sharedConfigOptions: SharedConfigOptions 19 | 20 | @Option( 21 | name: [.customLong("config")], 22 | help: "Path to config.yml" 23 | ) 24 | public var configPath: AbsolutePath 25 | 26 | public init() {} 27 | 28 | public mutating func run() throws { 29 | SetupLabelsCommandRunner().run(self, sharedConfigOptions: sharedConfigOptions, commandConfigPath: configPath) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Sources/Swiftlane/Commands/SetupLabelsCommand/SetupLabelsCommandRunner.swift: -------------------------------------------------------------------------------- 1 | 2 | import Foundation 3 | import GitLabAPI 4 | import Guardian 5 | import Simulator 6 | import SwiftlaneCore 7 | import Yams 8 | 9 | public struct SetupLabelsCommandConfig: Decodable { 10 | public let labelsConfigPath: String 11 | } 12 | 13 | public struct SetupLabelsCommandRunner: CommandRunnerProtocol { 14 | public func run( 15 | params _: SetupLabelsCommandParamsAccessing, 16 | commandConfig: SetupLabelsCommandConfig, 17 | sharedConfig _: SharedConfigData 18 | ) throws { 19 | let environmentValueReader: EnvironmentValueReading = DependenciesFactory.resolve() 20 | 21 | let filesManager: FSManaging = DependenciesFactory.resolve() 22 | 23 | let expandedLabelsConfigPath = try environmentValueReader.expandVariables( 24 | in: commandConfig.labelsConfigPath 25 | ) 26 | 27 | let labelsConfig: SetupLabelsTask.LabelsConfig = try filesManager.decode( 28 | try AbsolutePath(expandedLabelsConfigPath), 29 | decoder: YAMLDecoder() 30 | ) 31 | 32 | let taskConfig = SetupLabelsTask.Config( 33 | labelsConfig: labelsConfig 34 | ) 35 | 36 | let task = SetupLabelsTask( 37 | logger: DependenciesFactory.resolve(), 38 | config: taskConfig, 39 | gitlabCIEnvironment: DependenciesFactory.resolve(), 40 | gitlabApi: DependenciesFactory.resolve() 41 | ) 42 | 43 | try task.run() 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Sources/Swiftlane/Commands/SetupReviewersCommand/SetupReviewersCommand.swift: -------------------------------------------------------------------------------- 1 | 2 | import ArgumentParser 3 | import Foundation 4 | import SwiftlaneCore 5 | 6 | public protocol SetupReviewersCommandParamsAccessing { 7 | var sharedConfigOptions: SharedConfigOptions { get } 8 | var configPath: AbsolutePath { get } 9 | } 10 | 11 | /// CLI command to change status in jira issue 12 | public struct SetupReviewersCommand: ParsableCommand, SetupReviewersCommandParamsAccessing { 13 | public static var configuration = CommandConfiguration( 14 | commandName: "setup-reviewers", 15 | abstract: "Parse config and update reviewers in Merge Request" 16 | ) 17 | 18 | @OptionGroup public var sharedConfigOptions: SharedConfigOptions 19 | 20 | @Option( 21 | name: [.customLong("config")], 22 | help: "Path to config.yml" 23 | ) 24 | public var configPath: AbsolutePath 25 | 26 | public init() {} 27 | 28 | public mutating func run() throws { 29 | SetupReviewersCommandRunner().run(self, sharedConfigOptions: sharedConfigOptions, commandConfigPath: configPath) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Sources/Swiftlane/Commands/SwiftlaneCommand.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import ArgumentParser 4 | import Foundation 5 | import Guardian 6 | import Simulator 7 | import SwiftlaneCore 8 | import Yams 9 | 10 | public protocol SwiftlaneCommand: ParsableCommand { 11 | var commonOptions: CommonOptions { get } 12 | 13 | func runCMD() throws 14 | } 15 | 16 | public extension SwiftlaneCommand { 17 | func run() throws { 18 | DependenciesFactory.registerLoggerProducer(commons: commonOptions) 19 | DependenciesFactory.registerProducers() 20 | 21 | CommonRunner(logger: DependenciesFactory.resolve()).run { 22 | try runCMD() 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Sources/Swiftlane/Commands/UploadGitLabPackageCommand/UploadGitLabPackageCommand.swift: -------------------------------------------------------------------------------- 1 | 2 | import ArgumentParser 3 | import Foundation 4 | import SwiftlaneCore 5 | 6 | public protocol UploadGitLabPackageCommandParamsAccessing { 7 | var sharedConfigOptions: SharedConfigOptions { get } 8 | 9 | var projectID: UInt { get } 10 | var packageName: String { get } 11 | var packageVersion: String { get } 12 | var file: AbsolutePath { get } 13 | var uploadedFileName: String? { get } 14 | var timeoutSeconds: TimeInterval { get } 15 | } 16 | 17 | public struct UploadGitLabPackageCommand: ParsableCommand, UploadGitLabPackageCommandParamsAccessing { 18 | public static var configuration = CommandConfiguration( 19 | commandName: "upload-gitlab-package", 20 | abstract: "Uploads a package to GitLab packages" 21 | ) 22 | 23 | @OptionGroup public var sharedConfigOptions: SharedConfigOptions 24 | 25 | @Option(help: "GitLab project ID.") 26 | public var projectID: UInt 27 | 28 | @Option(help: "Name of new package.") 29 | public var packageName: String 30 | 31 | @Option(help: "Version of new package.") 32 | public var packageVersion: String 33 | 34 | @Option(help: "Path to file which should be uploaded.") 35 | public var file: AbsolutePath 36 | 37 | @Option(help: "File name in the GitLab package. Defaults to name of file from --file value.") 38 | public var uploadedFileName: String? 39 | 40 | @Option(help: "Upload timeout.") 41 | public var timeoutSeconds: TimeInterval = 600 42 | 43 | public init() {} 44 | 45 | public mutating func run() throws { 46 | UploadGitLabPackageCommandRunner().run(self, sharedConfigOptions: sharedConfigOptions) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Sources/Swiftlane/Commands/UploadGitLabPackageCommand/UploadGitLabPackageCommandRunner.swift: -------------------------------------------------------------------------------- 1 | 2 | import Foundation 3 | import Git 4 | import GitLabAPI 5 | import Guardian 6 | import Networking 7 | import Simulator 8 | import SwiftlaneCore 9 | import Xcodebuild 10 | import Yams 11 | 12 | public struct UploadGitLabPackageCommandRunner: CommandRunnerProtocol { 13 | public func run( 14 | params: UploadGitLabPackageCommandParamsAccessing, 15 | commandConfig _: Void, 16 | sharedConfig _: SharedConfigData 17 | ) throws { 18 | let taskConfig = UploadGitLabPackageTask.Config( 19 | projectID: params.projectID, 20 | packageName: params.packageName, 21 | packageVersion: params.packageVersion, 22 | file: params.file, 23 | uploadedFileName: params.uploadedFileName, 24 | timeoutSeconds: params.timeoutSeconds 25 | ) 26 | 27 | let task = try TasksFactory.makeUploadGitLabPackageTask(taskConfig: taskConfig) 28 | 29 | try task.run() 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Sources/Swiftlane/Commands/UploadToAppStoreCommand/UploadToAppStoreCommandRunner.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import AppStoreConnectAPI 4 | import AppStoreConnectJWT 5 | import Foundation 6 | import Provisioning 7 | import Simulator 8 | import SwiftlaneCore 9 | import Xcodebuild 10 | import Yams 11 | 12 | public class UploadToAppStoreTask { 13 | private let uploader: AppStoreConnectIPAUploading 14 | 15 | public init(uploader: AppStoreConnectIPAUploading) { 16 | self.uploader = uploader 17 | } 18 | 19 | func upload(ipaPath: AbsolutePath, using uploader: AppStoreConnectIPAUploaderType) throws { 20 | try self.uploader.upload( 21 | ipaPath: ipaPath, 22 | using: uploader 23 | ) 24 | } 25 | } 26 | 27 | public class UploadToAppStoreCommandRunner: CommandRunnerProtocol { 28 | public func run( 29 | params: UploadToAppStoreCommandParamsAccessing, 30 | commandConfig _: Void, 31 | sharedConfig: SharedConfigData 32 | ) throws { 33 | let task = try TasksFactory.makeUploadToAppStoreTask( 34 | authKeyPath: params.authKeyPath, 35 | authKeyIssuerID: params.authKeyIssuerID, 36 | paths: sharedConfig.paths 37 | ) 38 | 39 | try task.upload( 40 | ipaPath: params.ipaPath, 41 | using: params.uploadTool 42 | ) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Sources/Swiftlane/CommonRunner/CommonRunner.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Foundation 4 | import GitLabAPI 5 | import Guardian 6 | import SwiftlaneCore 7 | 8 | /// Allows to run any throwing closure. 9 | /// 10 | /// When it catches an error it will log the error and exit with nonzero exit code. 11 | public class CommonRunner { 12 | private let logger: Logging 13 | private let exitor: Exiting 14 | 15 | public init( 16 | logger: Logging, 17 | exitor: Exiting = Exitor() 18 | ) { 19 | self.logger = logger 20 | self.exitor = exitor 21 | } 22 | 23 | private func makeGuardianRunnerIfPossible() -> GuardianCommonRunner? { 24 | do { 25 | let mergeRequestReporter: MergeRequestReporting = DependenciesFactory.resolve() 26 | 27 | try (mergeRequestReporter as? GitLabMergeRequestReporter)?.checkEnvironmentCorrect() 28 | 29 | logger.success("Running under Guardian.") 30 | return GuardianCommonRunner(reporter: mergeRequestReporter, logger: logger) 31 | } catch { 32 | logger.warn("Running under Guardian is not possible (\(error)).") 33 | return nil 34 | } 35 | } 36 | 37 | public func run(_ closure: @escaping () throws -> Void) { 38 | do { 39 | if let guardian = makeGuardianRunnerIfPossible() { 40 | try guardian.run(closure) 41 | } else { 42 | try closure() 43 | } 44 | } catch { 45 | logger.logError(error) 46 | exitor.exit(with: 1) 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Sources/Swiftlane/ConfigModels/PipelineConfigModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Foundation 4 | 5 | public struct PipelineConfigModel: Decodable { 6 | public struct Variable: Decodable { 7 | public let key: String 8 | public let value: String 9 | } 10 | 11 | public let variables: [Variable] 12 | } 13 | -------------------------------------------------------------------------------- /Sources/Swiftlane/Extensions/AnyDecoder+.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Foundation 4 | import SwiftlaneCore 5 | import XMLCoder 6 | import Yams 7 | 8 | extension XMLDecoder: AnyDecoder {} 9 | extension YAMLDecoder: AnyDecoder {} 10 | -------------------------------------------------------------------------------- /Sources/Swiftlane/Extensions/GitLabAPIClient+.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import GitLabAPI 4 | import SwiftlaneCore 5 | 6 | public extension GitLabAPIClient { 7 | /// - Parameters: 8 | /// - accessTokenHeaderKey: if you are using `CI_JOB_TOKEN` then set this to `"JOB-TOKEN"` instead of `"PRIVATE-TOKEN"`. 9 | convenience init( 10 | logger: Logging, 11 | logLevel: LoggingLevel = .silent, 12 | environmentReader: EnvironmentValueReading, 13 | baseURLEnvKey: ShellEnvKeyRepresentable = ShellEnvKey.GITLAB_API_ENDPOINT, 14 | accessTokenEnvKey: ShellEnvKeyRepresentable = ShellEnvKey.PROJECT_ACCESS_TOKEN, 15 | accessTokenHeaderKey: String = "PRIVATE-TOKEN" 16 | ) throws { 17 | self.init( 18 | baseURL: try environmentReader.url(baseURLEnvKey), 19 | accessToken: try environmentReader.string(accessTokenEnvKey), 20 | accessTokenHeaderKey: accessTokenHeaderKey, 21 | logLevel: logLevel, 22 | logger: logger 23 | ) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Sources/Swiftlane/Extensions/Initable.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Foundation 4 | 5 | public protocol Initable { 6 | init() 7 | } 8 | -------------------------------------------------------------------------------- /Sources/Swiftlane/Extensions/JiraAPIClient+.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Foundation 4 | import JiraAPI 5 | import SwiftlaneCore 6 | 7 | public extension JiraAPIClient { 8 | convenience init( 9 | requestsTimeout: TimeInterval, 10 | logger: Logging, 11 | logLevel: LoggingLevel = .silent, 12 | baseURLEnvKey: ShellEnvKeyRepresentable = ShellEnvKey.JIRA_API_ENDPOINT, 13 | accessTokenEnvKey: ShellEnvKeyRepresentable = ShellEnvKey.JIRA_API_TOKEN, 14 | environmentReader: EnvironmentValueReading 15 | ) throws { 16 | self.init( 17 | baseURL: try environmentReader.url(baseURLEnvKey), 18 | accessToken: try environmentReader.string(accessTokenEnvKey), 19 | requestsTimeout: requestsTimeout, 20 | logger: logger, 21 | logLevel: logLevel 22 | ) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Sources/Swiftlane/Extensions/MergeRequestReportFactory.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Foundation 4 | import Guardian 5 | 6 | public extension MergeRequestReportFactory { 7 | convenience init() { 8 | self.init(captionProvider: MergeRequestReportCaptionProvider( 9 | ciToolName: "🦾🤖 Swiftlane (\(UTILL_VERSION))" 10 | )) 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Sources/Swiftlane/Extensions/Path+.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import ArgumentParser 4 | import SwiftlaneCore 5 | 6 | extension Path: ExpressibleByArgument { 7 | public init?(argument: String) { 8 | try? self.init(argument) 9 | } 10 | } 11 | 12 | extension RelativePath: ExpressibleByArgument { 13 | public init?(argument: String) { 14 | try? self.init(argument) 15 | } 16 | } 17 | 18 | extension AbsolutePath: ExpressibleByArgument { 19 | public init?(argument: String) { 20 | try? self.init(argument) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Sources/Swiftlane/Extensions/SemVer+.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import ArgumentParser 4 | import Foundation 5 | import SwiftlaneCore 6 | 7 | extension SemVer: ExpressibleByArgument { 8 | /// ExpressibleByArgument 9 | public init?(argument: String) { 10 | try? self.init(parseFrom: argument) 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Sources/Swiftlane/Extensions/SensitiveData+Argument.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import ArgumentParser 4 | import Foundation 5 | import SwiftlaneCore 6 | 7 | extension SensitiveData: ExpressibleByArgument where T: ExpressibleByArgument { 8 | public init?(argument: String) { 9 | guard let value = T(argument: argument) else { 10 | return nil 11 | } 12 | self.init(value) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Sources/Swiftlane/Extensions/URL+.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import ArgumentParser 4 | import Foundation 5 | 6 | extension URL: ExpressibleByArgument { 7 | public init?(argument: String) { 8 | self.init(string: argument) 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Sources/Swiftlane/RootCommandRunner.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import ArgumentParser 4 | import Foundation 5 | import PerfectRainbow 6 | import SwiftlaneCore 7 | 8 | public protocol LogOutputTypeSettable { 9 | static var outputTarget: OutputTarget { get set } 10 | } 11 | 12 | extension Rainbow: LogOutputTypeSettable {} 13 | 14 | public class RootCommandRunner { 15 | public let logsOutputTypeSetter: LogOutputTypeSettable.Type 16 | public let bufferSetter: StdIOWrapping 17 | public let xcodeChecker: XcodeChecking 18 | 19 | public init( 20 | logsOutputTypeSetter: LogOutputTypeSettable.Type, 21 | bufferSetter: StdIOWrapping, 22 | xcodeChecker: XcodeChecker 23 | ) { 24 | self.logsOutputTypeSetter = logsOutputTypeSetter 25 | self.bufferSetter = bufferSetter 26 | self.xcodeChecker = xcodeChecker 27 | } 28 | 29 | private func setupConsoleInteractionParams() { 30 | // Make our stdout unbuffered. 31 | bufferSetter.setupbuf(stdoutp: __stdoutp, wtf: nil) 32 | 33 | // Force Rainbow to output colors. 34 | if !xcodeChecker.isRunningFromXcode { 35 | logsOutputTypeSetter.outputTarget = .console 36 | } 37 | } 38 | 39 | public func run(_: T.Type, arguments: [String]? = nil) { 40 | setupConsoleInteractionParams() 41 | 42 | T.main(arguments) 43 | } 44 | } 45 | 46 | public extension RootCommandRunner { 47 | convenience init() { 48 | self.init( 49 | logsOutputTypeSetter: Rainbow.self, 50 | bufferSetter: StdIOWrapper(), 51 | xcodeChecker: XcodeChecker() 52 | ) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Sources/Swiftlane/Services/ArtifactsCollector.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Foundation 4 | import SwiftlaneCore 5 | 6 | public class ArtifactsCollector { 7 | public let filesManager: FSManaging 8 | public let logger: Logging 9 | 10 | public init( 11 | filesManager: FSManaging, 12 | logger: Logging 13 | ) { 14 | self.filesManager = filesManager 15 | self.logger = logger 16 | } 17 | 18 | public func collectArtifacts(dirs: [AbsolutePath], targetDir: AbsolutePath) throws { 19 | logger.verbose("Collecting artifacts: \(dirs)...") 20 | try? filesManager.delete(targetDir) 21 | try filesManager.mkdir(targetDir) 22 | 23 | try dirs.forEach { dir in 24 | let destination = targetDir.appending(path: dir.lastComponent) 25 | try filesManager.copy(dir, to: destination) 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Sources/Swiftlane/Services/ExpiringToDo/ExpiringToDoSorter.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Foundation 4 | 5 | public protocol ExpiringToDoSorting { 6 | func sort(todos: [VerifiedExpiringTodoModel]) -> [VerifiedExpiringTodoModel] 7 | } 8 | 9 | public class ExpiringToDoSorter: ExpiringToDoSorting { 10 | public func sort(todos: [VerifiedExpiringTodoModel]) -> [VerifiedExpiringTodoModel] { 11 | let otherToDos = todos.filter { 12 | if case .approachingExpiryDate = $0.status { 13 | return false 14 | } 15 | return true 16 | } 17 | 18 | /// Sort approaching todos by `daysLeft` before reporting. 19 | let approachingToDos = todos 20 | .compactMap { todo -> (VerifiedExpiringTodoModel, daysLeft: UInt)? in 21 | if case let .approachingExpiryDate(daysLeft) = todo.status { 22 | return (todo, daysLeft: daysLeft) 23 | } 24 | return nil 25 | } 26 | .sorted { lhs, rhs in 27 | lhs.daysLeft < rhs.daysLeft 28 | } 29 | .map(\.0) 30 | 31 | return otherToDos + approachingToDos 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Sources/Swiftlane/Services/MergeRequestInfoProvider/MergeRequestInfo.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Foundation 4 | 5 | public enum MergeRequestInfo { 6 | /// Abstraction describing a merge request's author. 7 | /// 8 | /// ATM it is suited to describe a GitLab user. 9 | public struct Author: Decodable, Hashable { 10 | public let id: Int 11 | public let name: String 12 | public let username: String 13 | public let avatarUrl: String? 14 | public let webUrl: String? 15 | 16 | public init( 17 | id: Int, 18 | name: String, 19 | username: String, 20 | avatarUrl: String?, 21 | webUrl: String? 22 | ) { 23 | self.id = id 24 | self.name = name 25 | self.username = username 26 | self.avatarUrl = avatarUrl 27 | self.webUrl = webUrl 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Sources/Swiftlane/Services/MultiScan/MultiScan.Config.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Foundation 4 | import Simulator 5 | import SwiftlaneCore 6 | import Xcodebuild 7 | 8 | public extension MultiScan { 9 | struct Config { 10 | public let builderConfig: Builder.Config 11 | public let testsRunnerConfig: TestsRunner.Config 12 | public let referenceSimulator: Simulator 13 | public let simulatorsCount: UInt 14 | public let resultsDir: AbsolutePath 15 | public let mergedXCResultPath: AbsolutePath 16 | public let mergedJUnitPath: AbsolutePath 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Sources/Swiftlane/Services/Parsers/AnyParser.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | public struct AnyParser: Parsing { 4 | public private(set) var _parse: (String) throws -> T 5 | 6 | public init(_ delegatee: V) where V.ResultType == T { 7 | _parse = delegatee.parse 8 | } 9 | 10 | public func parse(from: String) throws -> T { 11 | try _parse(from) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Sources/Swiftlane/Services/Parsers/JiraParsers/IssueKey/JiraIssueKeyParser.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Foundation 4 | import SwiftlaneCore 5 | 6 | public protocol JiraIssueKeyParsing { 7 | /// Parse all jira issue keys from string. 8 | func parse(from string: String) throws -> [String] 9 | } 10 | 11 | // Parser of jira issue key. 12 | public struct JiraIssueKeyParser { 13 | public let jiraProjectKey: String 14 | 15 | public init(jiraProjectKey: String) { 16 | self.jiraProjectKey = jiraProjectKey 17 | } 18 | } 19 | 20 | extension JiraIssueKeyParser: JiraIssueKeyParsing, Parsing { 21 | /// Parse all jira issue keys from string. 22 | public func parse(from string: String) throws -> [String] { 23 | let regex = try NSRegularExpression( 24 | // "(? ResultType 9 | } 10 | -------------------------------------------------------------------------------- /Sources/Swiftlane/Services/Periphery/Models/PeripheryModels.Accessibility.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Foundation 4 | 5 | // swiftformat:disable all 6 | 7 | public extension PeripheryModels { 8 | /// From periphery 2.9.0 release. 9 | /// See https://github.com/peripheryapp/periphery/blob/master/Sources/PeripheryKit/Indexer/Accessibility.swift 10 | enum Accessibility: String, Codable { 11 | case `public` = "public" 12 | case `internal` = "internal" 13 | case `private` = "private" 14 | case `fileprivate` = "fileprivate" 15 | case `open` = "open" 16 | case unknown = "" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Sources/Swiftlane/Services/Periphery/Models/PeripheryModels.Annotation.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Foundation 4 | 5 | // swiftformat:disable all 6 | 7 | public extension PeripheryModels { 8 | /// From periphery 2.9.0 release. 9 | /// See https://github.com/peripheryapp/periphery/blob/master/Sources/PeripheryKit/ScanResult.swift 10 | enum Annotation: String, Codable { 11 | case unused 12 | case assignOnlyProperty 13 | case redundantProtocol 14 | case redundantPublicAccessibility 15 | /// See https://github.com/peripheryapp/periphery/blob/master/Sources/Frontend/Formatters/OutputFormatter.swift 16 | case redundantConformance 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Sources/Swiftlane/Services/Periphery/Models/PeripheryModels.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Foundation 4 | 5 | // swiftformat:disable all 6 | 7 | public enum PeripheryModels { 8 | // Namespace. 9 | } 10 | -------------------------------------------------------------------------------- /Sources/Swiftlane/Services/Periphery/PeripheryResultsFormatting.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Foundation 4 | 5 | public protocol PeripheryResultsFormatting { 6 | func format(results: [PeripheryModels.ScanResult]) throws -> String 7 | } 8 | -------------------------------------------------------------------------------- /Sources/Swiftlane/Services/ProjectVersioning/ProjectVersionConverting.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Foundation 4 | import SwiftlaneCore 5 | 6 | public protocol ProjectVersionConverting { 7 | func convertAppVersionToPlistValue(_ version: SemVer) throws -> String 8 | func convertAppVersionFromPlistValue(_ version: String?) throws -> SemVer 9 | } 10 | 11 | public class StraightforwardProjectVersionConverter: Initable, ProjectVersionConverting { 12 | public required init() {} 13 | 14 | public func convertAppVersionToPlistValue(_ version: SemVer) throws -> String { 15 | version.string(format: .full) 16 | } 17 | 18 | public func convertAppVersionFromPlistValue(_ version: String?) throws -> SemVer { 19 | let unwrapped = try version.unwrap(errorDescription: "Is your project version in Info.plist nil?") 20 | return try SemVer(parseFrom: unwrapped) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Sources/Swiftlane/Services/RangesWelder.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Foundation 4 | 5 | // sourcery: AutoMockable 6 | public protocol RangesWelding { 7 | /// [1,2,3,5,8,9] -> ["1...3", "5", "8...9"] 8 | func weldRanges(from numbers: [T]) -> [String] 9 | } 10 | 11 | public class RangesWelder: RangesWelding { 12 | private class Range: CustomStringConvertible { 13 | public var start: T 14 | public var end: T 15 | 16 | public init(start: T) { 17 | self.start = start 18 | end = start 19 | } 20 | 21 | public var description: String { 22 | start == end ? "\(start)" : "\(start)...\(end)" 23 | } 24 | } 25 | 26 | public init() {} 27 | 28 | /// [1,2,3,5,8,9] -> ["1...3", "5", "8...9"] 29 | public func weldRanges(from numbers: [T]) -> [String] { 30 | var ranges = [Range]() 31 | 32 | numbers.forEach { line in 33 | if ranges.last?.end == line - 1 { 34 | ranges.last?.end = line 35 | } else { 36 | ranges.append(Range(start: line)) 37 | } 38 | } 39 | 40 | return ranges.map(\.description) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Sources/Swiftlane/Services/Scan/Scan.Config.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Foundation 4 | import Simulator 5 | import SwiftlaneCore 6 | 7 | public extension Scan { 8 | struct Config { 9 | public let referenceSimulator: Simulator 10 | public let resultsDir: AbsolutePath 11 | public let logsPath: AbsolutePath 12 | public let scheme: String 13 | public let mergedXCResultPath: AbsolutePath 14 | public let mergedJUnitPath: AbsolutePath 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Sources/Swiftlane/Services/SimulatorLogsChecker.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Foundation 4 | import SwiftlaneCore 5 | 6 | public class SimulatorLogsChecker { 7 | public struct Config { 8 | public let checkSimulatorsLogsScriptPath: AbsolutePath 9 | } 10 | 11 | public let shell: ShellExecuting 12 | public let config: Config 13 | 14 | public init( 15 | shell: ShellExecuting, 16 | config: Config 17 | ) { 18 | self.shell = shell 19 | self.config = config 20 | } 21 | 22 | public func check(systemLogsDir: AbsolutePath) throws { 23 | _ = try shell.run( 24 | "\(config.checkSimulatorsLogsScriptPath) '\(systemLogsDir)' '*.log'", 25 | log: .commandAndOutput(outputLogLevel: .debug) 26 | ) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Sources/Swiftlane/Services/WarningLimits/WarningsStorage.Config.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Foundation 4 | import SwiftlaneCore 5 | 6 | public extension WarningsStorage { 7 | struct Config { 8 | public let projectDir: AbsolutePath 9 | public let warningsJsonsFolder: AbsolutePath 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Sources/Swiftlane/Services/junit/JUnitTestSuites.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Foundation 4 | 5 | public struct JUnitTestSuites: Codable { 6 | public let testsuite: [TestSuite] 7 | public let name: String 8 | public let tests: Int 9 | public let failures: Int 10 | 11 | public struct TestSuite: Codable { 12 | public let name: String 13 | public let tests: Int 14 | public let failures: Int 15 | public let testcase: [TestCase] 16 | 17 | public struct TestCase: Codable { 18 | public let classname: String 19 | public let name: String 20 | public let time: Float? 21 | 22 | public let failure: [Failure]? 23 | 24 | public struct Failure: Codable, Equatable { 25 | public let message: String 26 | public let file: String 27 | 28 | public enum CodingKeys: String, CodingKey { 29 | case message 30 | case file = "" 31 | } 32 | } 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Sources/Swiftlane/Services/xccov/XCCOVCoverageReport.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Foundation 4 | 5 | public struct XCCOVCoverageReport: Decodable, Equatable { 6 | /// Total coverage percent 7 | public let lineCoverage: Double 8 | public let targets: [XCCOVTargetCoverage] 9 | } 10 | 11 | public struct XCCOVTargetCoverage: Decodable, Equatable { 12 | /// Product name of the target 13 | /// e.g. `SMDeeplinks.framework` 14 | public let name: String 15 | public let executableLines: Int 16 | public let coveredLines: Int 17 | /// Coverage of target from 0 to 1 18 | public let lineCoverage: Double 19 | public let files: [XCCOVFileCoverage] 20 | 21 | public var realTargetName: String { 22 | String(name.split(separator: ".").first!) 23 | } 24 | } 25 | 26 | public struct XCCOVFileCoverage: Decodable, Equatable { 27 | /// e.g. `AppDelegate.swift` 28 | public let name: String 29 | /// Absolute path to file 30 | public let path: String 31 | public let executableLines: Int 32 | public let coveredLines: Int 33 | /// Coverage of file from 0 to 1 34 | public let lineCoverage: Double 35 | } 36 | -------------------------------------------------------------------------------- /Sources/Swiftlane/Services/xccov/XCCOVService.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Foundation 4 | import SwiftlaneCore 5 | 6 | // sourcery: AutoMockable 7 | public protocol XCCOVServicing { 8 | func generateAndParseCoverageReport( 9 | xcresultPath: AbsolutePath, 10 | generatedCoverageFilePath: AbsolutePath 11 | ) throws -> XCCOVCoverageReport 12 | } 13 | 14 | public class XCCOVService { 15 | private let filesManager: FSManaging 16 | private let shell: ShellExecuting 17 | 18 | public init( 19 | filesManager: FSManaging, 20 | shell: ShellExecuting 21 | ) { 22 | self.filesManager = filesManager 23 | self.shell = shell 24 | } 25 | } 26 | 27 | extension XCCOVService: XCCOVServicing { 28 | public func generateAndParseCoverageReport( 29 | xcresultPath: AbsolutePath, 30 | generatedCoverageFilePath: AbsolutePath 31 | ) throws -> XCCOVCoverageReport { 32 | try shell.run( 33 | "xcrun xccov view --report --json \"\(xcresultPath)\" > \"\(generatedCoverageFilePath)\"", 34 | log: .commandOnly 35 | ) 36 | 37 | let data = try filesManager.readData(generatedCoverageFilePath, log: true) 38 | let decoder = JSONDecoder() 39 | let result = try decoder.decode(XCCOVCoverageReport.self, from: data) 40 | return result 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Sources/Swiftlane/SharedConfigOptions.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import ArgumentParser 4 | import Foundation 5 | import SwiftlaneCore 6 | 7 | public enum LogLevelOption: String, ExpressibleByArgument { 8 | case silent 9 | case error 10 | case warning 11 | case important 12 | case info 13 | case debug 14 | case verbose 15 | } 16 | 17 | public extension LoggingLevel { 18 | init(from option: LogLevelOption) { 19 | switch option { 20 | case .verbose: 21 | self = .verbose 22 | case .debug: 23 | self = .debug 24 | case .info: 25 | self = .info 26 | case .warning: 27 | self = .warning 28 | case .error: 29 | self = .error 30 | case .silent: 31 | self = .silent 32 | case .important: 33 | self = .important 34 | } 35 | } 36 | } 37 | 38 | public struct SharedConfigOptions: ParsableArguments { 39 | @Option(help: "Project dir path.") 40 | public var projectDir: AbsolutePath 41 | 42 | @Option( 43 | name: [.customLong("shared-config")], 44 | help: "Path to shared-config.yml" 45 | ) 46 | public var sharedConfigPath: AbsolutePath 47 | 48 | @OptionGroup public var commonOptions: CommonOptions 49 | 50 | public init() {} 51 | } 52 | 53 | public struct CommonOptions: ParsableArguments { 54 | @Option(help: "Logging level.") 55 | public var logLevel: LogLevelOption = .info 56 | 57 | @Option(help: "Path to file which will contain verbose logs (always verbose regardless --log-level option).") 58 | public var verboseLogfile: AbsolutePath? 59 | 60 | public init() {} 61 | } 62 | -------------------------------------------------------------------------------- /Sources/Swiftlane/ShellEnvKey.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import SwiftlaneCore 4 | 5 | public enum ShellEnvKey: String { 6 | case GITLAB_API_ENDPOINT 7 | case PROJECT_ACCESS_TOKEN 8 | case JIRA_API_TOKEN 9 | case JIRA_API_ENDPOINT 10 | case GIT_AUTHOR_EMAIL 11 | case GITLAB_GROUP_DEV_TEAM_ID_TO_FETCH_MEMBERS 12 | case ADP_ARTIFACTS_REPO 13 | } 14 | 15 | extension ShellEnvKey: ShellEnvKeyRepresentable { 16 | public var asShellEnvKey: String { 17 | rawValue 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Sources/Swiftlane/StdIOWrapper.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Foundation 4 | 5 | public protocol StdIOWrapping { 6 | func setupbuf(stdoutp: UnsafeMutablePointer!, wtf: UnsafeMutablePointer!) 7 | } 8 | 9 | public struct StdIOWrapper {} 10 | 11 | extension StdIOWrapper: StdIOWrapping { 12 | public func setupbuf(stdoutp: UnsafeMutablePointer!, wtf: UnsafeMutablePointer!) { 13 | setbuf(stdoutp, wtf) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Sources/Swiftlane/Tasks/BuildAppTask/BuildAppTask.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Foundation 4 | import Xcodebuild 5 | 6 | public final class BuildAppTask { 7 | private let builder: BuilderProtocol 8 | 9 | private let buildForTesting: Bool 10 | private let destination: BuildDestination 11 | 12 | public init( 13 | builder: BuilderProtocol, 14 | buildForTesting: Bool, 15 | destination: BuildDestination 16 | ) { 17 | self.builder = builder 18 | self.buildForTesting = buildForTesting 19 | self.destination = destination 20 | } 21 | 22 | public func run() throws { 23 | try builder.build(forTesting: buildForTesting, destination: destination) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Sources/Swiftlane/Tasks/CertsTasks/CertsInstallTask.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Foundation 4 | 5 | import AppStoreConnectAPI 6 | import Provisioning 7 | import Simulator 8 | import SwiftlaneCore 9 | import Xcodebuild 10 | 11 | public final class CertsInstallTask { 12 | private let logger: Logging 13 | private let shell: ShellExecuting 14 | private let installer: CertsInstalling 15 | 16 | private let config: CertsInstallConfig 17 | 18 | public init( 19 | logger: Logging, 20 | shell: ShellExecuting, 21 | installer: CertsInstalling, 22 | config: CertsInstallConfig 23 | ) { 24 | self.logger = logger 25 | self.shell = shell 26 | self.installer = installer 27 | self.config = config 28 | } 29 | 30 | @discardableResult 31 | public func run() throws -> [(profile: MobileProvision, installPath: AbsolutePath)] { 32 | try installer.installCertificatesAndProfiles(config: config) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Sources/Swiftlane/Tasks/CertsTasks/CertsUpdateTask.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import AppStoreConnectAPI 4 | import Foundation 5 | import Provisioning 6 | import Simulator 7 | import SwiftlaneCore 8 | import Xcodebuild 9 | 10 | public final class CertsUpdateTask { 11 | private let logger: Logging 12 | private let shell: ShellExecuting 13 | private let certsService: CertsUpdating 14 | 15 | private let config: CertsUpdateConfig 16 | 17 | public init( 18 | logger: Logging, 19 | shell: ShellExecuting, 20 | certsService: CertsUpdating, 21 | config: CertsUpdateConfig 22 | ) { 23 | self.logger = logger 24 | self.shell = shell 25 | self.certsService = certsService 26 | self.config = config 27 | } 28 | 29 | public func run() throws { 30 | try certsService.updateCertificatesAndProfiles( 31 | updateConfig: config 32 | ) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Sources/Swiftlane/Tasks/CheckCommitsTask/CheckCommitsTask.swift: -------------------------------------------------------------------------------- 1 | 2 | import Foundation 3 | import Guardian 4 | import SwiftlaneCore 5 | 6 | public final class CheckCommitsTask { 7 | private let checker: CommitsChecking 8 | private let reporter: MergeRequestReporting 9 | 10 | public init( 11 | checker: CommitsChecking, 12 | reporter: MergeRequestReporting 13 | ) { 14 | self.checker = checker 15 | self.reporter = reporter 16 | } 17 | 18 | public func run() throws { 19 | try checker.checkCommits() 20 | 21 | try reporter.createOrUpdateReport() 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Sources/Swiftlane/Tasks/CheckCommitsTask/Checkers/CommitsCheckerEnReporter.swift: -------------------------------------------------------------------------------- 1 | 2 | import Foundation 3 | import Guardian 4 | 5 | // sourcery: AutoMockable 6 | public protocol CommitsCheckerReporting { 7 | func reportSuccess() 8 | func reportFailsDetected(_ failsCommits: [String]) 9 | } 10 | 11 | public class CommitsCheckerEnReporter { 12 | private let reporter: MergeRequestReporting 13 | 14 | public init( 15 | reporter: MergeRequestReporting 16 | ) { 17 | self.reporter = reporter 18 | } 19 | } 20 | 21 | extension CommitsCheckerEnReporter: CommitsCheckerReporting { 22 | public func reportSuccess() { 23 | reporter.success("The list of your comments has been checked. Everything is fine with you, go to the next window") 24 | } 25 | 26 | public func reportFailsDetected(_ failsCommits: [String]) { 27 | failsCommits 28 | .map { 29 | "ALARM! A commit loss has occurred: `\($0)`" 30 | } 31 | .forEach { 32 | reporter.fail($0) 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Sources/Swiftlane/Tasks/CheckStopListTask/Checkers/ContentChecker/ContentCheckerEnReporter.swift: -------------------------------------------------------------------------------- 1 | 2 | import Foundation 3 | import Guardian 4 | 5 | // sourcery: AutoMockable 6 | public protocol ContentCheckerReporting { 7 | func reportSuccess() 8 | func reportFailsDetected(_ fails: [ContentChecker.FileBadLinesInfo]) 9 | } 10 | 11 | public class ContentCheckerEnReporter { 12 | private let reporter: MergeRequestReporting 13 | private let rangesWelder: RangesWelding 14 | 15 | public init( 16 | reporter: MergeRequestReporting, 17 | rangesWelder: RangesWelding 18 | ) { 19 | self.reporter = reporter 20 | self.rangesWelder = rangesWelder 21 | } 22 | } 23 | 24 | extension ContentCheckerEnReporter: ContentCheckerReporting { 25 | public func reportSuccess() { 26 | reporter.success("Everything is in full chocolate. No prohibited changes were found in the files.") 27 | } 28 | 29 | // swiftformat:disable indent 30 | public func reportFailsDetected(_ fails: [ContentChecker.FileBadLinesInfo]) { 31 | fails 32 | .map { 33 | "In \($0.file) changes prohibited on the territory of GitLab have been noticed – \($0.errorObject) in strings: " 34 | + rangesWelder.weldRanges(from: $0.lines).joined(separator: ", ") 35 | } 36 | .forEach { 37 | reporter.fail($0) 38 | } 39 | } 40 | // swiftformat:enable indent 41 | } 42 | -------------------------------------------------------------------------------- /Sources/Swiftlane/Tasks/CheckStopListTask/Checkers/FilesChecker/FilesCheckerEnReporter.swift: -------------------------------------------------------------------------------- 1 | 2 | import Foundation 3 | import Guardian 4 | 5 | // sourcery: AutoMockable 6 | public protocol FilesCheckerReporting { 7 | func reportSuccess() 8 | func reportFailsDetected(_ fails: [FilesChecker.BadFileInfo]) 9 | } 10 | 11 | public class FilesCheckerEnReporter { 12 | private let reporter: MergeRequestReporting 13 | 14 | public init( 15 | reporter: MergeRequestReporting 16 | ) { 17 | self.reporter = reporter 18 | } 19 | } 20 | 21 | extension FilesCheckerEnReporter: FilesCheckerReporting { 22 | public func reportSuccess() { 23 | reporter.success("No banned files or changes found") 24 | } 25 | 26 | public func reportFailsDetected(_ fails: [FilesChecker.BadFileInfo]) { 27 | fails 28 | .map { 29 | "You have detected contraband: changes in the file `\($0.file)`" 30 | } 31 | .forEach { 32 | reporter.fail($0) 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Sources/Swiftlane/Tasks/GuardianAfterBuildTask/Checkers/BuildErrorsChecker/BuildErrorsChecker.Config.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Foundation 4 | import SwiftlaneCore 5 | 6 | public extension BuildErrorsChecker { 7 | struct Config { 8 | public let projectDir: AbsolutePath 9 | public let derivedDataPath: AbsolutePath 10 | public let htmlReportOutputDir: AbsolutePath 11 | public let jsonReportOutputFilePath: AbsolutePath 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Sources/Swiftlane/Tasks/GuardianAfterBuildTask/Checkers/BuildErrorsChecker/BuildErrorsReporter.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Foundation 4 | import Guardian 5 | import SwiftlaneCore 6 | 7 | // sourcery: AutoMockable 8 | public protocol BuildErrorsReporting { 9 | func report(errors: [XCLogParserIssuesReport.Issue], projectDir: AbsolutePath) 10 | } 11 | 12 | public class BuildErrorsReporter: BuildErrorsReporting { 13 | private let reporter: MergeRequestReporting 14 | private let issueFormatter: XCLogParserIssueFormatting 15 | 16 | public init( 17 | reporter: MergeRequestReporting, 18 | issueFormatter: XCLogParserIssueFormatting 19 | ) { 20 | self.reporter = reporter 21 | self.issueFormatter = issueFormatter 22 | } 23 | 24 | // swiftformat:disable indent 25 | public func report(errors: [XCLogParserIssuesReport.Issue], projectDir: AbsolutePath) { 26 | let rows = errors.map { error in 27 | let prettyDescription = issueFormatter.format(issue: error, projectDir: projectDir) 28 | 29 | return """ 30 | 31 | 32 | \(prettyDescription) 33 | 34 | 35 | """ 36 | }.joined(separator: "\n\n") 37 | 38 | reporter.fail("Build failed 🥲") 39 | reporter.markdown(""" 40 | ### Build errors 41 | 42 | 43 | 44 | 47 | 48 | \(rows) 49 |
45 | Errors 46 |
50 | """) 51 | } 52 | // swiftformat:enable indent 53 | } 54 | -------------------------------------------------------------------------------- /Sources/Swiftlane/Tasks/GuardianAfterBuildTask/Checkers/ChangesCoverageChecker/ChangesCoverageReporter.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Foundation 4 | import Guardian 5 | 6 | public class ChangesCoverageReporter { 7 | private let reporter: MergeRequestReporting 8 | 9 | public init( 10 | reporter: MergeRequestReporting 11 | ) { 12 | self.reporter = reporter 13 | } 14 | } 15 | 16 | // swiftformat:disable indent 17 | extension ChangesCoverageReporter: ChangesCoverageReporting { 18 | public func reportSuccess() { 19 | reporter.success("Code Coverage is ok.") 20 | } 21 | 22 | public func reportCheckIsDisabledForSourceBranch(sourceBranch: String) { 23 | reporter.message("Limits of Code Coverage are not checked for source branch: `\(sourceBranch)`.") 24 | } 25 | 26 | public func reportCheckIsDisabledForTargetBranch(targetBranch: String) { 27 | reporter.message("Limits of Code Coverage are not checked for target branch: `\(targetBranch)`.") 28 | } 29 | 30 | public func reportViolation(_ violation: ChangesCoverageLimitChecker.Violation, limit: Int) { 31 | func percent(from double: Double) -> String { 32 | "\(Int(round(double * 100)))%" 33 | } 34 | 35 | reporter.fail( 36 | "Code Coverage of changes is *(\(percent(from: violation.coverageOfChangedLines)))* " + 37 | "less than the limit *\(limit)%* for file \(violation.file)" 38 | ) 39 | } 40 | } 41 | 42 | // swiftformat:enable indent 43 | -------------------------------------------------------------------------------- /Sources/Swiftlane/Tasks/GuardianAfterBuildTask/Checkers/ChangesCoverageChecker/ChangesCoverageReporting.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Foundation 4 | 5 | // sourcery: AutoMockable 6 | public protocol ChangesCoverageReporting { 7 | func reportSuccess() 8 | func reportCheckIsDisabledForSourceBranch(sourceBranch: String) 9 | func reportCheckIsDisabledForTargetBranch(targetBranch: String) 10 | func reportViolation(_ violation: ChangesCoverageLimitChecker.Violation, limit: Int) 11 | } 12 | -------------------------------------------------------------------------------- /Sources/Swiftlane/Tasks/GuardianAfterBuildTask/Checkers/TargetCoverageChecker/TargetCoverageReporting.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Foundation 4 | import SwiftlaneCore 5 | 6 | // sourcery: AutoMockable 7 | public protocol TargetCoverageReporting { 8 | func reportAllTargetsCoverage(targets: [CalculatedTargetCoverage]) 9 | func reportCoverageLimitsSuccess() 10 | func reportCoverageLimitsCheckFailed(violation: TargetsCoverageLimitChecker.Violation) 11 | } 12 | -------------------------------------------------------------------------------- /Sources/Swiftlane/Tasks/GuardianAfterBuildTask/Checkers/TargetCoverageChecker/TargetsCoverageTargetsFilterer.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Foundation 4 | import SwiftlaneCore 5 | 6 | // sourcery: AutoMockable 7 | public protocol TargetsCoverageTargetsFiltering { 8 | func filterTargets( 9 | report: XCCOVCoverageReport, 10 | allowedProductNameSuffixes: [String], 11 | excludeTargetsNames: [StringMatcher] 12 | ) -> [XCCOVTargetCoverage] 13 | } 14 | 15 | public class TargetsCoverageTargetsFilterer { 16 | public init() {} 17 | } 18 | 19 | extension TargetsCoverageTargetsFilterer: TargetsCoverageTargetsFiltering { 20 | public func filterTargets( 21 | report: XCCOVCoverageReport, 22 | allowedProductNameSuffixes: [String], 23 | excludeTargetsNames: [StringMatcher] 24 | ) -> [XCCOVTargetCoverage] { 25 | report.targets 26 | .filter { target in 27 | allowedProductNameSuffixes.contains { 28 | target.name.hasSuffix($0) 29 | } 30 | } 31 | .filter { target in 32 | !excludeTargetsNames.isMatching(string: target.realTargetName) 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Sources/Swiftlane/Tasks/GuardianAfterBuildTask/Checkers/UnitTestsExitCodeChecker/UnitTestsExitCodeChecker.Config.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Foundation 4 | import SwiftlaneCore 5 | 6 | public extension UnitTestsExitCodeChecker { 7 | struct Config { 8 | public let projectDir: AbsolutePath 9 | public let logsDir: AbsolutePath 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Sources/Swiftlane/Tasks/GuardianAfterBuildTask/Checkers/UnitTestsResultsChecker/UnitTestsResultsChecker.Config.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Foundation 4 | import SwiftlaneCore 5 | 6 | public extension UnitTestsResultsChecker { 7 | struct Config { 8 | public let junitPath: AbsolutePath 9 | public let projectDir: AbsolutePath 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Sources/Swiftlane/Tasks/GuardianAfterBuildTask/Checkers/UnitTestsResultsChecker/UnitTestsResultsReporter.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Foundation 4 | import Guardian 5 | 6 | public class UnitTestsResultsReporter { 7 | private let reporter: MergeRequestReporting 8 | 9 | public init( 10 | reporter: MergeRequestReporting 11 | ) { 12 | self.reporter = reporter 13 | } 14 | } 15 | 16 | // swiftformat:disable indent 17 | extension UnitTestsResultsReporter: UnitTestsResultsReporting { 18 | public func failedToParseJUnit(error: Error, jobUrl: String) { 19 | let jobLogsText = "Job logs" 20 | let jobLogsLinkText = "[\(jobLogsText)](\(jobUrl))" 21 | reporter.fail( 22 | "Check \(jobLogsLinkText), " + 23 | "it looks like build or tests have failed but I couldn't determine the reason 😢" 24 | ) 25 | reporter.markdown("```log\nError decoding junit report: \(String(reflecting: error))\n```") 26 | } 27 | 28 | public func failedUnitTestsDetected(_ failures: [UnitTestsResultsChecker.Failure]) { 29 | let rows = failures.map { 30 | """ 31 | 32 | 33 |

\($0.failure.file)

34 |

\($0.testCaseName)

35 |
36 | 			\($0.failure.message)
37 | 			
38 | 39 | 40 | """ 41 | }.joined(separator: "\n") 42 | 43 | reporter.fail("Tests failed :(") 44 | reporter.markdown(""" 45 | ### UnitTests Failed 46 | 47 | 48 | 49 | 52 | 53 | \(rows) 54 |
50 | Failed Tests 51 |
55 | """) 56 | } 57 | } 58 | 59 | // swiftformat:enable indent 60 | -------------------------------------------------------------------------------- /Sources/Swiftlane/Tasks/GuardianAfterBuildTask/Checkers/UnitTestsResultsChecker/UnitTestsResultsReporting.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Foundation 4 | 5 | // sourcery: AutoMockable 6 | public protocol UnitTestsResultsReporting { 7 | func failedToParseJUnit(error: Error, jobUrl: String) 8 | func failedUnitTestsDetected(_ failures: [UnitTestsResultsChecker.Failure]) 9 | } 10 | -------------------------------------------------------------------------------- /Sources/Swiftlane/Tasks/GuardianAfterBuildTask/GuardianAfterBuildTask.Config.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Foundation 4 | import SwiftlaneCore 5 | 6 | public extension GuardianAfterBuildTask { 7 | struct Config { 8 | public let projectDir: AbsolutePath 9 | public let buildErrorsCheckerConfig: BuildErrorsChecker.Config 10 | public let buildWarningsCheckerConfig: BuildWarningsChecker.Config 11 | public let unitTestsResultsCheckerConfig: UnitTestsResultsChecker.Config 12 | public let exitCodeCheckerConfig: UnitTestsExitCodeChecker.Config 13 | public let changesCoverageLimitCheckerConfig: ChangesCoverageLimitChecker.Config? 14 | public let targetsCoverageLimitCheckerConfig: TargetsCoverageLimitChecker.Config 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Sources/Swiftlane/Tasks/GuardianBeforeBuildTask/Checkers/ExpiringToDoChecker/ExpiringToDoAllowedAuthorsProvider.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Foundation 4 | import SwiftlaneCore 5 | import GitLabAPI 6 | import Guardian 7 | 8 | public protocol ExpiringToDoAllowedAuthorsProviding { 9 | func allowedToDoAuthors() throws -> [String] 10 | } 11 | 12 | public class GitLabExpiringToDoAllowedAuthorsProvider: ExpiringToDoAllowedAuthorsProviding { 13 | private let logger: Logging 14 | private let gitlabApi: GitLabAPIClientProtocol 15 | 16 | public let gitlabGroupIDToFetchMembers: Int 17 | 18 | /// - Parameters: 19 | /// - gitlabGroupIDToFetchMembers: members of the groups will be treated as possible authors of a TODO. 20 | public init( 21 | logger: Logging, 22 | gitlabApi: GitLabAPIClientProtocol, 23 | gitlabGroupIDToFetchMembers: Int 24 | ) { 25 | self.logger = logger 26 | self.gitlabApi = gitlabApi 27 | self.gitlabGroupIDToFetchMembers = gitlabGroupIDToFetchMembers 28 | } 29 | 30 | private func groupMembers() throws -> [Member] { 31 | 32 | try gitlabApi.groupMembers( 33 | group: GitLab.Group(id: gitlabGroupIDToFetchMembers) 34 | ).await() 35 | } 36 | 37 | public func allowedToDoAuthors() throws -> [String] { 38 | let gitlabMembers = try groupMembers() 39 | .filter { !$0.username.contains("r2d2") } 40 | .map(\.username) 41 | 42 | logger.debug("GitLab group has \(gitlabMembers.count) members: \(gitlabMembers)") 43 | return gitlabMembers 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Sources/Swiftlane/Tasks/GuardianBeforeBuildTask/Checkers/FilePathChecker/FilePathReporter.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Foundation 4 | import Guardian 5 | import SwiftlaneCore 6 | 7 | public protocol FilePathReporting { 8 | func reportInvalidFilePath(_ path: String) 9 | } 10 | 11 | public class FilePathReporter { 12 | private let reporter: MergeRequestReporting 13 | 14 | public init( 15 | reporter: MergeRequestReporting 16 | ) { 17 | self.reporter = reporter 18 | } 19 | } 20 | 21 | extension FilePathReporter: FilePathReporting { 22 | public func reportInvalidFilePath(_ path: String) { 23 | reporter.fail("File path contains forbidden characters: `\(path)`") 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Sources/Swiftlane/Tasks/GuardianBeforeBuildTask/Checkers/WarningLimitsChecker/WarningLimitsCheckerConfig.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import SwiftlaneCore 4 | 5 | public struct WarningLimitsCheckerConfig { 6 | public let projectDir: AbsolutePath 7 | public let swiftlintConfigPath: AbsolutePath 8 | public let trackingPushRemoteName: String /// e.g. `"origin"` 9 | public let trackingNewFoldersCommitMessage: String 10 | public let loweringWarningLimitsCommitMessage: String 11 | public let committeeName: String 12 | public let committeeEmail: String 13 | public let testableTargetsListFile: Path 14 | } 15 | -------------------------------------------------------------------------------- /Sources/Swiftlane/Tasks/GuardianBeforeBuildTask/Checkers/WarningLimitsChecker/WarningLimitsCheckerReporting.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Foundation 4 | 5 | // sourcery: AutoMockable 6 | public protocol WarningLimitsCheckerReporting { 7 | func warningLimitsAreCorrect() 8 | func warningLimitsHaveBeenLowered() 9 | func warningLimitsViolated( 10 | violations: [WarningLimitsChecker.Violation], 11 | newWarningsGroupedByMessage: [[SwiftLintViolation]] 12 | ) 13 | func newWarningLimitsHaveBeenTracked() 14 | } 15 | -------------------------------------------------------------------------------- /Sources/Swiftlane/Tasks/GuardianBeforeBuildTask/GuardianBeforeBuildTask.Config.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Foundation 4 | import SwiftlaneCore 5 | 6 | public extension GuardianBeforeBuildTask { 7 | struct WarningLimitsConfig { 8 | public let projectDir: AbsolutePath 9 | public let swiftlintConfigPath: AbsolutePath 10 | public let loweringWarningLimitsCommitMessage: String 11 | public let trackingNewFoldersCommitMessage: String 12 | public let remoteName: String 13 | public let committeeName: String 14 | public let committeeEmail: String 15 | public let warningsStorageConfig: WarningsStorage.Config 16 | public let testableTargetsListFile: Path 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Sources/Swiftlane/Tasks/GuardianCheckAuthorTask/GuardianCheckAuthorTask.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Foundation 4 | import Git 5 | import Guardian 6 | import SwiftlaneCore 7 | 8 | public final class GuardianCheckAuthorTask { 9 | private let logger: Logging 10 | private let reporter: MergeRequestReporting 11 | private let mergeRequestAuthorChecker: MergeRequestAuthorChecking 12 | private let gitlabCIEnvironmentReader: GitLabCIEnvironmentReading 13 | 14 | public init( 15 | logger: Logging, 16 | mergeRequestReporter: MergeRequestReporting, 17 | mergeRequestAuthorChecker: MergeRequestAuthorChecking, 18 | gitlabCIEnvironmentReader: GitLabCIEnvironmentReading 19 | ) { 20 | self.logger = logger 21 | self.reporter = mergeRequestReporter 22 | self.mergeRequestAuthorChecker = mergeRequestAuthorChecker 23 | self.gitlabCIEnvironmentReader = gitlabCIEnvironmentReader 24 | } 25 | 26 | public func run() throws { 27 | try mergeRequestAuthorChecker.check() 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Sources/Swiftlane/Tasks/RunTestsTask/RunTestsTask.Config.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Foundation 4 | import SwiftlaneCore 5 | 6 | public extension RunTestsTask { 7 | struct Config { 8 | public let projectDir: AbsolutePath 9 | public let projectFile: AbsolutePath 10 | public let scheme: String 11 | public let deviceModel: String 12 | public let osVersion: String 13 | public let simulatorsCount: UInt 14 | public let testPlan: String? 15 | public let derivedDataDir: AbsolutePath 16 | public let testRunsDerivedDataDir: AbsolutePath 17 | public let logsDir: AbsolutePath 18 | public let resultsDir: AbsolutePath 19 | public let mergedXCResultPath: AbsolutePath 20 | public let mergedJUnitPath: AbsolutePath 21 | public let testWithoutBuilding: Bool 22 | public let useMultiScan: Bool 23 | public let xcodebuildFormatterCommand: String 24 | public let testingTimeout: TimeInterval 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Sources/Swiftlane/Tasks/RunTestsTask/RunTestsTask.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Foundation 4 | 5 | import Simulator 6 | import SwiftlaneCore 7 | import Xcodebuild 8 | 9 | public protocol TestRunPerforming { 10 | func runTests() throws -> [TestsRunner.TestRunResult] 11 | } 12 | 13 | extension MultiScan: TestRunPerforming { 14 | public func runTests() throws -> [TestsRunner.TestRunResult] { 15 | try self.run() 16 | } 17 | } 18 | 19 | extension Scan: TestRunPerforming { 20 | public func runTests() throws -> [TestsRunner.TestRunResult] { 21 | try [self.run()] 22 | } 23 | } 24 | 25 | public final class RunTestsTask { 26 | private let logger: Logging 27 | private let exitor: Exiting 28 | private let testRunPerformer: TestRunPerforming 29 | 30 | public init( 31 | logger: Logging, 32 | exitor: Exiting, 33 | testRunPerformer: TestRunPerforming 34 | ) { 35 | self.logger = logger 36 | self.exitor = exitor 37 | self.testRunPerformer = testRunPerformer 38 | } 39 | 40 | public func run() throws { 41 | let results = try testRunPerformer.runTests() 42 | 43 | let errors = results.compactMap(\.result.error) 44 | errors.forEach { 45 | logger.logError($0) 46 | } 47 | if let worstCode = errors.map(\.reason.rawValue).min() { 48 | exitor.exit(with: Int32(worstCode)) 49 | } 50 | logger.success("RunTestsTask: Success.") 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Sources/Swiftlane/Tasks/SetProvisioningTask/SetProvisioningTask.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Foundation 4 | import Provisioning 5 | import SwiftlaneCore 6 | import Xcodebuild 7 | 8 | public struct SetProvisioningTaskConfig { 9 | public let xcodeprojPath: AbsolutePath 10 | public let schemeName: String 11 | public let buildConfigurationName: String 12 | public let provisionProfileName: String 13 | } 14 | 15 | public final class SetProvisioningTask { 16 | private let provisioningProfileService: ProvisioningProfilesServicing 17 | private let projectPatcher: XcodeProjectPatching 18 | 19 | private let config: SetProvisioningTaskConfig 20 | 21 | public init( 22 | provisioningProfileService: ProvisioningProfilesServicing, 23 | projectPatcher: XcodeProjectPatching, 24 | config: SetProvisioningTaskConfig 25 | ) { 26 | self.provisioningProfileService = provisioningProfileService 27 | self.projectPatcher = projectPatcher 28 | self.config = config 29 | } 30 | 31 | public func run() throws { 32 | let (profile, _) = try provisioningProfileService 33 | .findProvisioningProfile(named: config.provisionProfileName) 34 | 35 | try projectPatcher.setProvisionProfile( 36 | xcodeprojPath: config.xcodeprojPath, 37 | schemeName: config.schemeName, 38 | configurationName: config.buildConfigurationName, 39 | profileUUID: profile.UUID, 40 | profileName: profile.Name 41 | ) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Sources/Swiftlane/version.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | // CI creates releases based on this value. 4 | public let UTILL_VERSION: String = "0.10.0" 5 | -------------------------------------------------------------------------------- /Sources/SwiftlaneCLI/main.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Swiftlane 4 | 5 | RootCommandRunner().run( 6 | SwiftlaneRootCommand.self 7 | ) 8 | -------------------------------------------------------------------------------- /Sources/Xcodebuild/Builder/Builder.Config.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Foundation 4 | import SwiftlaneCore 5 | 6 | public extension Builder { 7 | struct Config { 8 | public let project: AbsolutePath 9 | public let scheme: String 10 | public let derivedDataPath: AbsolutePath 11 | public let logsPath: AbsolutePath 12 | public let configuration: String 13 | public let xcodebuildFormatterCommand: String 14 | 15 | /// Xcodebuild `build` or `build-for-testing` configuration. 16 | /// - Parameters: 17 | /// - project: Path to .xcodeproj file. 18 | /// - scheme: Scheme to build. 19 | /// - derivedDataPath: Derived data path. 20 | /// - logsPath: Path to directory where build logs will be stored. 21 | /// - configuration: Build configuration name (usually `Debug` or `Release`) 22 | public init( 23 | project: AbsolutePath, 24 | scheme: String, 25 | derivedDataPath: AbsolutePath, 26 | logsPath: AbsolutePath, 27 | configuration: String, 28 | xcodebuildFormatterCommand: String 29 | ) { 30 | self.project = project 31 | self.scheme = scheme 32 | self.derivedDataPath = derivedDataPath 33 | self.logsPath = logsPath 34 | self.configuration = configuration 35 | self.xcodebuildFormatterCommand = xcodebuildFormatterCommand 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Sources/Xcodebuild/TestsRunner/TestsRunner.Config.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Foundation 4 | import SwiftlaneCore 5 | 6 | public extension TestsRunner { 7 | struct Config { 8 | public let project: AbsolutePath 9 | public let projectDirPath: AbsolutePath 10 | public let scheme: String 11 | public let buildDerivedDataPath: AbsolutePath 12 | public let testRunsDerivedDataPath: AbsolutePath 13 | public let testRunsLogsPath: AbsolutePath 14 | public let testPlan: String? 15 | public let testWithoutBuilding: Bool 16 | public let xcodebuildFormatterCommand: String 17 | public let testingTimeout: TimeInterval 18 | 19 | public init( 20 | builderConfig: Builder.Config, 21 | projectDirPath: AbsolutePath, 22 | testRunsDerivedDataPath: AbsolutePath, 23 | testRunsLogsPath: AbsolutePath, 24 | testPlan: String?, 25 | testWithoutBuilding: Bool, 26 | xcodebuildFormatterCommand: String, 27 | testingTimeout: TimeInterval 28 | ) { 29 | project = builderConfig.project 30 | self.projectDirPath = projectDirPath 31 | scheme = builderConfig.scheme 32 | buildDerivedDataPath = builderConfig.derivedDataPath 33 | self.testRunsDerivedDataPath = testRunsDerivedDataPath 34 | self.testRunsLogsPath = testRunsLogsPath 35 | self.testPlan = testPlan 36 | self.testWithoutBuilding = testWithoutBuilding 37 | self.xcodebuildFormatterCommand = xcodebuildFormatterCommand 38 | self.testingTimeout = testingTimeout 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Sources/Xcodebuild/TestsRunner/TestsRunner.Errors.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Foundation 4 | 5 | public extension TestsRunner { 6 | enum Errors: Error { 7 | case xctestrunFileNotFound 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Sources/Xcodebuild/TestsRunner/TestsRunner.TestRunResult.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Foundation 4 | 5 | import Simulator 6 | import SwiftlaneCore 7 | 8 | public extension TestsRunner { 9 | struct TestRunResult { 10 | public let simulator: Simulator 11 | public let tests: [XCTestFunction] 12 | public let xcresultPath: AbsolutePath? 13 | public let runLogsPaths: LogsPathPair 14 | public let junitPath: AbsolutePath? 15 | public let result: Result 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Sources/Xcodebuild/XCTestProductsServices/README.md: -------------------------------------------------------------------------------- 1 | ## About built XCTest products 2 | 3 | ### `.xctestrun` 4 | 5 | It's a plist xml file. 6 | 7 | We can find paths to all built `.xctest` packages inside a `.xctestrun` file. 8 | 9 | Location example: 10 | ``` 11 | /Build/Products/__.xctestrun 12 | ``` 13 | 14 | ### `.xctest` 15 | 16 | It's a folder built for concrete test target (e.g. `SMRunTests.xctest`). 17 | 18 | Each `.xctest` contains: 19 | 20 | * **`` executable binary ** 21 | * **`.xctestplan` files ** 22 | * `_CodeSignature` folder 23 | * `Frameworks` folder 24 | * `Info.plist` 25 | 26 | Location example: 27 | ``` 28 | /Build/Products/Debug-iphonesimulator/-Runner.app/PlugIns/.xctest 29 | ``` 30 | 31 | ### `.xctestplan` 32 | 33 | It's a json file describing test plan configuration (including skipped of selected tests). 34 | 35 | Location example: 36 | ``` 37 | /Build/Products/Debug-iphonesimulator/-Runner.app/PlugIns/.xctest/BaseTestPlan.xctestplan 38 | ``` 39 | 40 | -------------------------------------------------------------------------------- /Sources/Xcodebuild/XCTestProductsServices/XCTestRun/XCTestRunFinder.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Foundation 4 | import SwiftlaneCore 5 | 6 | // sourcery: AutoMockable 7 | public protocol XCTestRunFinding { 8 | func findXCTestRunFile(derivedDataPath: AbsolutePath) throws -> AbsolutePath 9 | } 10 | 11 | public class XCTestRunFinder { 12 | public enum Errors: Error { 13 | case xcTestRunFileNotFound(inDerivedDataPath: String) 14 | } 15 | 16 | let filesManager: FSManaging 17 | 18 | public init( 19 | filesManager: FSManaging 20 | ) { 21 | self.filesManager = filesManager 22 | } 23 | } 24 | 25 | extension XCTestRunFinder: XCTestRunFinding { 26 | // swiftformat:disable indent 27 | public func findXCTestRunFile(derivedDataPath: AbsolutePath) throws -> AbsolutePath { 28 | guard 29 | let path = try filesManager.find(derivedDataPath) 30 | .first(where: { $0.string.hasSuffix(".xctestrun") }) 31 | else { 32 | throw Errors.xcTestRunFileNotFound(inDerivedDataPath: derivedDataPath.string) 33 | } 34 | return path 35 | } 36 | // swiftformat:enable indent 37 | } 38 | -------------------------------------------------------------------------------- /Sources/Xcodebuild/XcodebuildCommandProducer.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Foundation 4 | 5 | // sourcery: AutoMockable 6 | public protocol XcodebuildCommandProducing { 7 | func produce() -> String 8 | } 9 | 10 | public struct XcodebuildCommandProducer { 11 | let shouldUseRosetta: Bool 12 | 13 | public init(shouldUseRosetta: Bool) { 14 | self.shouldUseRosetta = shouldUseRosetta 15 | } 16 | } 17 | 18 | extension XcodebuildCommandProducer: XcodebuildCommandProducing { 19 | public func produce() -> String { 20 | [ 21 | "env NSUnbufferedIO=YES", 22 | shouldUseRosetta ? "arch -x86_64" : nil, 23 | "xcodebuild" 24 | ] 25 | .compactMap { $0 } 26 | .joined(separator: " ") 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Tests/AppStoreConnectAPITests/AppStoreConnectAPITests.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import AppStoreConnectAPI 4 | import Foundation 5 | import SwiftlaneCore 6 | import XCTest 7 | 8 | class AppStoreConnectAPITests: XCTestCase { 9 | func testFooBar() {} 10 | } 11 | -------------------------------------------------------------------------------- /Tests/ArgumentParserExtensionsTests/Stub.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | // This file needed just for remove Xcode warning util real tests will appear. 4 | -------------------------------------------------------------------------------- /Tests/FirebaseAPITests/FirebaseAPITests.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Combine 4 | import Foundation 5 | import Networking 6 | import SwiftlaneCore 7 | import SwiftlaneUnitTestTools 8 | import XCTest 9 | 10 | @testable import FirebaseAPI 11 | 12 | class FirebaseAPITests: XCTestCase {} 13 | -------------------------------------------------------------------------------- /Tests/GitLabAPITests/Stubs.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Foundation 4 | import Networking 5 | import SwiftlaneUnitTestTools 6 | import XCTest 7 | 8 | class Stubs { 9 | static let decoder = JSONDecoder() 10 | 11 | /// Read dumped request and response from `Tests/JiraAPITests/Stubs/`. 12 | /// - Parameters: 13 | /// - route: part of url respective to baseURL of a NetworkingClient. 14 | /// - uuid: uuid of request. 15 | static func readDump(route: String, uuid: String) throws -> NetworkingDumper.DumpEntry { 16 | let data = try Bundle.module.readStubData(path: "\(route)/\(uuid).json") 17 | return try decoder.decode(NetworkingDumper.DumpEntry.self, from: data) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Tests/GitLabAPITests/Stubs/projects/75700/merge_requests/3536/notes/0BBB909D-90F8-4462-9C02-6CA37727FFF6.json: -------------------------------------------------------------------------------- 1 | { 2 | "loggerRequestUUID": "0BBB909D-90F8-4462-9C02-6CA37727FFF6", 3 | "request": { 4 | "absoluteURL": "https://gitlab.host.com/api/v4//projects/75700/merge_requests/3536/notes?body=%23%20COMMENT%20BY%20C3PO%20TESTS%20%60createNote()%60%0A%0ATest%20mr%20BODY%0A%0A%23%23%23%20Multiline%20comment%0A%0A%60%60%60swift%0Afunc%20code()%20%7B%0A%09print(%22hello%20world!%22)%0A%7D%0A%60%60%60%0A%0A%F0%9F%98%8E", 5 | "baseURL": "https://gitlab.host.com/api/v4/", 6 | "method": "POST", 7 | "route": "/projects/75700/merge_requests/3536/notes", 8 | "timeout": 5 9 | }, 10 | "responseBody": "{\"id\": 434124, \"type\": null, \"body\": \"# COMMENT BY C3PO TESTS `createNote()`\\n\\nTest mr BODY\\n\\n### Multiline comment\\n\\n```swift\\nfunc code() {\\n\\tprint(\\\"hello world!\\\")\\n}\\n```\\n\\n\\ud83d\\ude0e\", \"attachment\": null, \"author\": {\"id\": 483, \"username\": \"douglasholmes\", \"name\": \"Luba-Katanga\", \"state\": \"active\", \"avatar_url\": \"https://gitlab.host.com/uploads/-/system/user/avatar/671/avatar.png\", \"web_url\": \"https://gitlab.host.com/douglasholmes\"}, \"created_at\": \"2022-02-01T11:30:38.715Z\", \"updated_at\": \"2022-02-01T11:30:38.715Z\", \"system\": false, \"noteable_id\": 59110, \"noteable_type\": \"MergeRequest\", \"resolvable\": false, \"confidential\": false, \"noteable_iid\": 3536, \"commands_changes\": {}}", 11 | "responseCode": 201 12 | } -------------------------------------------------------------------------------- /Tests/GitLabAPITests/Stubs/projects/75700/merge_requests/3536/notes/434124/ACBFAC5C-7573-4F3E-ACE7-E2271EC4548E.json: -------------------------------------------------------------------------------- 1 | { 2 | "loggerRequestUUID" : "ACBFAC5C-7573-4F3E-ACE7-E2271EC4548E", 3 | "request" : { 4 | "absoluteURL" : "https:\/\/gitlab.host.com\/api\/v4\/\/projects\/75700\/merge_requests\/3536\/notes\/434124", 5 | "baseURL" : "https:\/\/gitlab.host.com\/api\/v4\/", 6 | "method" : "DELETE", 7 | "route" : "\/projects\/75700\/merge_requests\/3536\/notes\/434124", 8 | "timeout" : 5 9 | }, 10 | "responseBody" : "", 11 | "responseCode" : 204 12 | } -------------------------------------------------------------------------------- /Tests/GitLabAPITests/Stubs/projects/75700/merge_requests/3536/notes/434124/CB1BD4B1-5386-49E2-8394-4E2DE5468BE3.json: -------------------------------------------------------------------------------- 1 | { 2 | "loggerRequestUUID": "CB1BD4B1-5386-49E2-8394-4E2DE5468BE3", 3 | "request": { 4 | "absoluteURL": "https://gitlab.host.com/api/v4//projects/75700/merge_requests/3536/notes/434124?body=%23%20COMMENT%20BY%20C3PO%20TESTS%20%60createNote()%60%0A%0ATest%20mr%20BODY%0A%0A%23%23%23%20Multiline%20comment%0A%0A%60%60%60swift%0Afunc%20code()%20%7B%0A%09print(%22hello%20world!%22)%0A%7D%0A%60%60%60%0A%0A%F0%9F%98%8E%0A%0AEDITED%20IN%20%60editNote(authorId:)%60%20AT%202022-02-01%2011:30:38%20+0000", 5 | "baseURL": "https://gitlab.host.com/api/v4/", 6 | "method": "PUT", 7 | "route": "/projects/75700/merge_requests/3536/notes/434124", 8 | "timeout": 5 9 | }, 10 | "responseBody": "{\"id\": 434124, \"type\": null, \"body\": \"# COMMENT BY C3PO TESTS `createNote()`\\n\\nTest mr BODY\\n\\n### Multiline comment\\n\\n```swift\\nfunc code() {\\n\\tprint(\\\"hello world!\\\")\\n}\\n```\\n\\n\\ud83d\\ude0e\\n\\nEDITED IN `editNote(authorId:)` AT 2022-02-01 11:30:38 0000\", \"attachment\": null, \"author\": {\"id\": 417, \"username\": \"douglasholmes\", \"name\": \"Luba-Katanga\", \"state\": \"active\", \"avatar_url\": \"https://gitlab.host.com/uploads/-/system/user/avatar/671/avatar.png\", \"web_url\": \"https://gitlab.host.com/douglasholmes\"}, \"created_at\": \"2022-02-01T11:30:38.715Z\", \"updated_at\": \"2022-02-01T11:30:39.244Z\", \"system\": false, \"noteable_id\": 79650, \"noteable_type\": \"MergeRequest\", \"resolvable\": false, \"confidential\": false, \"noteable_iid\": 3536, \"commands_changes\": {}}", 11 | "responseCode": 200 12 | } -------------------------------------------------------------------------------- /Tests/GitTests/Stubs/diffs/binary.diff: -------------------------------------------------------------------------------- 1 | diff --git a/might/often/single/among/national/capital/which/exactly/political/involve/little.png b/might/often/single/among/national/capital/which/exactly/political/involve/little.png 2 | new file mode 100644 3 | index 000000000..99f8085f9 4 | Binary files /dev/null and b/binary_files_differ_path differ 5 | -------------------------------------------------------------------------------- /Tests/GitTests/Stubs/diffs/changedOneLine.diff: -------------------------------------------------------------------------------- 1 | diff --git a/wear/recently/spring/minute/thousand/arrive.sh b/wear/recently/spring/minute/thousand/arrive.sh 2 | index 7e57f722dd..781995d0e4 100755 3 | --- a/wear/recently/spring/minute/thousand/arrive.sh 4 | +++ b/wear/recently/spring/minute/thousand/arrive.sh 5 | @@ -16,4 +16,4 @@ fi 6 | # Next command launch Xcode if not launched 7 | # but it seems it is better than killall command invocation 8 | # for non CI machines because more careful for unsaved changes 9 | -osascript -e 'tell app "Xcode" to close every window' & 10 | +# osascript -e 'tell app "Xcode" to close every window' & 11 | -------------------------------------------------------------------------------- /Tests/GitTests/Stubs/diffs/changedOneLine_and_addedOneLine.diff: -------------------------------------------------------------------------------- 1 | diff --git a/scripts/some_script.sh b/scripts/some_script.sh 2 | index c1bf6f992f..2966dff1ae 100755 3 | --- a/scripts/some_script.sh 4 | +++ b/scripts/some_script.sh 5 | @@ -3,6 +3,7 @@ 6 | set -euo pipefail 7 | 8 | export SM_UTILS_PATH="etc/utils/" 9 | +# some change 1 10 | export SM_UTILS_BIN_PATH="etc/bin/" 11 | 12 | # ==== Install staff ==== # 13 | @@ -19,7 +20,7 @@ echo "🧑🏻‍💻 Installing certificates..." 14 | 15 | ./bin/job --config $(pwd)/configs/some_config.yml 16 | 17 | - 18 | +# some change 2 19 | echo 20 | echo "🥳" 21 | echo 22 | -------------------------------------------------------------------------------- /Tests/GitTests/Stubs/diffs/chmod.diff: -------------------------------------------------------------------------------- 1 | diff --git a/Utils/rclone.conf b/Utils/rclone.conf 2 | old mode 100644 3 | new mode 100755 4 | -------------------------------------------------------------------------------- /Tests/GitTests/Stubs/diffs/deletedFile.diff: -------------------------------------------------------------------------------- 1 | diff --git a/ADTarget/Info.plist b/ADTarget/Info.plist 2 | deleted file mode 100644 3 | index e62c3f60cb..0000000000 4 | --- a/ADTarget/Info.plist 5 | +++ /dev/null 6 | @@ -1,22 +0,0 @@ 7 | - 8 | - 9 | - 10 | - 11 | - CFBundleDevelopmentRegion 12 | - $(DEVELOPMENT_LANGUAGE) 13 | - CFBundleExecutable 14 | - $(EXECUTABLE_NAME) 15 | - CFBundleIdentifier 16 | - $(PRODUCT_BUNDLE_IDENTIFIER) 17 | - CFBundleInfoDictionaryVersion 18 | - 6.0 19 | - CFBundleName 20 | - $(SM_PRODUCT_NAME) 21 | - CFBundlePackageType 22 | - $(PRODUCT_BUNDLE_PACKAGE_TYPE) 23 | - CFBundleShortVersionString 24 | - 1.0 25 | - CFBundleVersion 26 | - 19 27 | - 28 | - 29 | -------------------------------------------------------------------------------- /Tests/GitTests/Stubs/diffs/newEmptyFile.diff: -------------------------------------------------------------------------------- 1 | diff --git a/empty_file b/empty_file 2 | new file mode 100644 3 | index 0000000000..e69de29bb2 4 | -------------------------------------------------------------------------------- /Tests/GitTests/Stubs/diffs/newFile2.diff: -------------------------------------------------------------------------------- 1 | diff --git a/b b/b 2 | new file mode 100644 3 | index 0000000000..5e4413d571 4 | --- /dev/null 5 | +++ b/b 6 | @@ -0,0 +1 @@ 7 | +sample file named b 8 | -------------------------------------------------------------------------------- /Tests/GitTests/Stubs/diffs/newFileOneLine.diff: -------------------------------------------------------------------------------- 1 | diff --git a/new_file b/new_file 2 | new file mode 100644 3 | index 0000000000..16ecc154c2 4 | --- /dev/null 5 | +++ b/new_file 6 | @@ -0,0 +1 @@ 7 | +Wed Jul 6 14:09:39 MSK 2022 8 | -------------------------------------------------------------------------------- /Tests/GitTests/Stubs/diffs/newFileTrickyName.diff: -------------------------------------------------------------------------------- 1 | diff --git a/ a/ b/nice b/ a/ b/nice 2 | new file mode 100644 3 | index 0000000000..f2e67035b5 4 | --- /dev/null 5 | +++ b/ a/ b/nice 6 | @@ -0,0 +1 @@ 7 | +cr mlouke in folder named a/b/c 8 | -------------------------------------------------------------------------------- /Tests/GitTests/Stubs/diffs/noNewLine.diff: -------------------------------------------------------------------------------- 1 | diff --git a/file.yml b/file.yml 2 | index 7e57f722dd..781995d0e4 100644 3 | --- a/file.yml 4 | +++ b/file.yml 5 | @@ -2,4 +2,4 @@ deviceModel: "iPhone 11" 6 | osVersion: "15.5" 7 | simulatorsCount: 1 8 | scheme: SMAllTests 9 | -useMultiScan: false 10 | \ No newline at end of file 11 | +useMultiScan: true 12 | \ No newline at end of file 13 | -------------------------------------------------------------------------------- /Tests/GitTests/Stubs/diffs/pdf_deleted.diff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swiftlane-code/Swiftlane/f58eaff920b308d01e4b8c1faeb43fa632a2517a/Tests/GitTests/Stubs/diffs/pdf_deleted.diff -------------------------------------------------------------------------------- /Tests/GitTests/Stubs/diffs/renamedFile.diff: -------------------------------------------------------------------------------- 1 | diff --git a/project.yml b/pr.yml 2 | similarity index 100% 3 | rename from project.yml 4 | rename to pr.yml 5 | -------------------------------------------------------------------------------- /Tests/GitTests/Stubs/diffs/spacesAndBackslashDiffLine.diff: -------------------------------------------------------------------------------- 1 | diff --git a/public/social/listen/world/than/friend.swift b/public/social/listen/world/than/friend.swift 2 | index 25958219b5..b53d3e5a68 100644 3 | --- a/public/social/listen/world/than/friend.swift 4 | +++ b/public/social/listen/world/than/friend.swift 5 | @@ -275,25 +275,6 @@ extension Manager { 6 | } 7 | } 8 | 9 | - func hasGrandchildren(taxon: RealmTaxonVariant) -> Bool { 10 | - while index < children.count - 1, !hasGrandchildren { 11 | - } 12 | - return hasGrandchildren 13 | - } 14 | - 15 | /** 16 | Searches for a non-skippable descendant. 17 | 18 | @@ -302,7 +283,7 @@ extension Manager { 19 | ``` 20 | hier - A - B - C - D - D1 21 | \ 22 | - D1 23 | + D2 24 | 25 | For B - jumpFromId = A 26 | jumpToId = D 27 | -------------------------------------------------------------------------------- /Tests/JiraAPITests/Stubs.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Foundation 4 | import Networking 5 | import SwiftlaneUnitTestTools 6 | import XCTest 7 | 8 | class Stubs { 9 | static let decoder = JSONDecoder() 10 | 11 | /// Read dumped request and response from `Tests/JiraAPITests/Stubs/`. 12 | /// - Parameters: 13 | /// - route: part of url respective to baseURL of a NetworkingClient. 14 | /// - uuid: uuid of request. 15 | static func readDump(route: String, uuid: String) throws -> NetworkingDumper.DumpEntry { 16 | let data = try Bundle.module.readStubData(path: "\(route)/\(uuid).json") 17 | return try decoder.decode(NetworkingDumper.DumpEntry.self, from: data) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Tests/JiraAPITests/Stubs/project/MDDS/versions/9CE45543-6DF0-4E51-A74F-46407256FAB3.json: -------------------------------------------------------------------------------- 1 | { 2 | "loggerRequestUUID": "9CE45543-6DF0-4E51-A74F-46407256FAB3", 3 | "request": { 4 | "absoluteURL": "https://supercompany.atlassian.net/rest/api/3/project/MOBI/versions", 5 | "baseURL": "https://supercompany.atlassian.net/rest/api/3/", 6 | "method": "GET", 7 | "route": "project/MOBI/versions" 8 | }, 9 | "responseBody": "[{\"self\": \"https://supercompany.atlassian.net/rest/api/3/version/87858\", \"id\": \"87858\", \"name\": \"6.12\", \"archived\": false, \"released\": true, \"startDate\": \"2021-11-01\", \"releaseDate\": \"2021-11-19\", \"userStartDate\": \"01.11.21\", \"userReleaseDate\": \"19.11.21\", \"projectId\": 10181}, {\"self\": \"https://supercompany.atlassian.net/rest/api/3/version/77461\", \"id\": \"77461\", \"name\": \"6.13\", \"archived\": false, \"released\": true, \"startDate\": \"2021-11-15\", \"releaseDate\": \"2021-12-03\", \"userStartDate\": \"15.11.21\", \"userReleaseDate\": \"03.12.21\", \"projectId\": 10181}, {\"self\": \"https://supercompany.atlassian.net/rest/api/3/version/80757\", \"id\": \"45496\", \"name\": \"6.14\", \"archived\": false, \"released\": false, \"startDate\": \"2021-11-29\", \"releaseDate\": \"2021-12-15\", \"overdue\": false, \"userStartDate\": \"29.11.21\", \"userReleaseDate\": \"15.12.21\", \"projectId\": 10181}]", 10 | "responseCode": 200 11 | } -------------------------------------------------------------------------------- /Tests/JiraAPITests/Stubs/project/MDDS/versions/_9CE45543-6DF0-4E51-A74F-46407256FAB3.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "self": "https://supercompany.atlassian.net/rest/api/3/version/87858", 4 | "id": "87858", 5 | "name": "6.12", 6 | "archived": false, 7 | "released": true, 8 | "startDate": "2021-11-01", 9 | "releaseDate": "2021-11-19", 10 | "userStartDate": "01.11.21", 11 | "userReleaseDate": "19.11.21", 12 | "projectId": 10181 13 | }, 14 | { 15 | "self": "https://supercompany.atlassian.net/rest/api/3/version/77461", 16 | "id": "77461", 17 | "name": "6.13", 18 | "archived": false, 19 | "released": true, 20 | "startDate": "2021-11-15", 21 | "releaseDate": "2021-12-03", 22 | "userStartDate": "15.11.21", 23 | "userReleaseDate": "03.12.21", 24 | "projectId": 10181 25 | }, 26 | { 27 | "self": "https://supercompany.atlassian.net/rest/api/3/version/80757", 28 | "id": "45496", 29 | "name": "6.14", 30 | "archived": false, 31 | "released": false, 32 | "startDate": "2021-11-29", 33 | "releaseDate": "2021-12-15", 34 | "overdue": false, 35 | "userStartDate": "29.11.21", 36 | "userReleaseDate": "15.12.21", 37 | "projectId": 10181 38 | } 39 | ] -------------------------------------------------------------------------------- /Tests/JiraAPITests/Stubs/version/10561/D41C3AE6-F7C5-4CD7-9494-E90B335712C3.json: -------------------------------------------------------------------------------- 1 | { 2 | "loggerRequestUUID": "D41C3AE6-F7C5-4CD7-9494-E90B335712C3", 3 | "request": { 4 | "absoluteURL": "https://supercompany.atlassian.net/rest/api/3/version/10561?expand=issuesstatus", 5 | "baseURL": "https://supercompany.atlassian.net/rest/api/3/", 6 | "method": "GET", 7 | "route": "version/10561" 8 | }, 9 | "responseBody": "{\"self\": \"https://supercompany.atlassian.net/rest/api/3/version/80757\", \"id\": \"80757\", \"name\": \"6.14\", \"archived\": false, \"released\": false, \"startDate\": \"2021-11-29\", \"releaseDate\": \"2021-12-15\", \"overdue\": false, \"userStartDate\": \"29.11.21\", \"userReleaseDate\": \"15.12.21\", \"projectId\": 10181, \"issuesStatusForFixVersion\": {\"unmapped\": 0, \"toDo\": 35, \"inProgress\": 29, \"done\": 81}}", 10 | "responseCode": 200 11 | } -------------------------------------------------------------------------------- /Tests/JiraAPITests/Stubs/version/10561/_D41C3AE6-F7C5-4CD7-9494-E90B335712C3.json: -------------------------------------------------------------------------------- 1 | { 2 | "self": "https://supercompany.atlassian.net/rest/api/3/version/80757", 3 | "id": "80757", 4 | "name": "6.14", 5 | "archived": false, 6 | "released": false, 7 | "startDate": "2021-11-29", 8 | "releaseDate": "2021-12-15", 9 | "overdue": false, 10 | "userStartDate": "29.11.21", 11 | "userReleaseDate": "15.12.21", 12 | "projectId": 10181, 13 | "issuesStatusForFixVersion": { 14 | "unmapped": 0, 15 | "toDo": 35, 16 | "inProgress": 29, 17 | "done": 81 18 | } 19 | } -------------------------------------------------------------------------------- /Tests/ProvisioningTests/KeychainServiceTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Foundation 4 | import Git 5 | import SwiftlaneCore 6 | import XCTest 7 | 8 | @testable import Provisioning 9 | 10 | class KeychainServiceTests: XCTestCase { 11 | // foo 12 | } 13 | -------------------------------------------------------------------------------- /Tests/ProvisioningTests/Stubs/match_AppStore_com.fakecompany.app.mobileprovision: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swiftlane-code/Swiftlane/f58eaff920b308d01e4b8c1faeb43fa632a2517a/Tests/ProvisioningTests/Stubs/match_AppStore_com.fakecompany.app.mobileprovision -------------------------------------------------------------------------------- /Tests/ProvisioningTests/Stubs/rsa_key.pub: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA5dkoSVGjuB7XLG2D0yaJ 3 | jUlMh8ngZkzayJqT+5mPKS344s5Bi/BciIJMV8yWJinMmmTDcA4FDet+Z6lhXbE/ 4 | Bkwy0GAuXkEsDpxh78o7UFlVpL4+XN4ubI8a6f8IBaJJEK264j0WmEOe52eeaCbP 5 | IxACUQG3wJOUIzcHhtS+3Gwn0eG4PrThASTMx7QGFX6pKFaFPROH2gMpIhj/se6t 6 | qwhCmBZrqdjtk+dM6sOFkWdfkk8Zo5dgjUgI/3eoAvZ4G/FtMmG5CeJ9zpwwEIzt 7 | /JJz21s30gnSxiRg4DmD9V7o3o5kL6b0wkAlDyJHL/IfchCYjHr9Thk19iywHGtM 8 | YwIDAQAB 9 | -----END PUBLIC KEY----- 10 | -------------------------------------------------------------------------------- /Tests/ProvisioningTests/Stubs/rsa_key_csr: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE REQUEST----- 2 | MIICYjCCAUoCAQAwHTEbMBkGA1UEAwwSU3dpZnRsYW5lIENTUiB0ZXN0MIIBIjAN 3 | BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA5dkoSVGjuB7XLG2D0yaJjUlMh8ng 4 | ZkzayJqT+5mPKS344s5Bi/BciIJMV8yWJinMmmTDcA4FDet+Z6lhXbE/Bkwy0GAu 5 | XkEsDpxh78o7UFlVpL4+XN4ubI8a6f8IBaJJEK264j0WmEOe52eeaCbPIxACUQG3 6 | wJOUIzcHhtS+3Gwn0eG4PrThASTMx7QGFX6pKFaFPROH2gMpIhj/se6tqwhCmBZr 7 | qdjtk+dM6sOFkWdfkk8Zo5dgjUgI/3eoAvZ4G/FtMmG5CeJ9zpwwEIzt/JJz21s3 8 | 0gnSxiRg4DmD9V7o3o5kL6b0wkAlDyJHL/IfchCYjHr9Thk19iywHGtMYwIDAQAB 9 | oAAwDQYJKoZIhvcNAQELBQADggEBAFhdd/qL2G95OxL8lud7vuyVl3M+ODaJfMYf 10 | SRqaCoGbMmh5YerKUTHHiv4QVP+hGVGPGmA9dhWgFf6JuE8wVaVcIX8VJe1shubU 11 | zxcybr5/2tp9kAu6VgpYg+MxzOlhDZOMyRJZc4nfynHKYbPz+S/Pv/+D/n4mMmBk 12 | TOrGq/8xbx4JQqWtFg+NwSVzPef7OzZo4CFQbV5hgmvfZHqH7ynKYBwkVONJwqLt 13 | yO0vIaoF5xsK+/dJ7TgYkBDRCr1atONtHn0yDzz7mtsNRKtrJ7B3Ajrx96m/IN15 14 | 5yjJRfTc+aXgGpEeUE0L29xV/lVrI+AhTVlg1Du3O8IiNGKXPTM= 15 | -----END CERTIFICATE REQUEST----- 16 | -------------------------------------------------------------------------------- /Tests/ProvisioningTests/Stubs/self-signed-ssl.cer: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swiftlane-code/Swiftlane/f58eaff920b308d01e4b8c1faeb43fa632a2517a/Tests/ProvisioningTests/Stubs/self-signed-ssl.cer -------------------------------------------------------------------------------- /Tests/SimulatorTests/Mocks/TestError.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | enum TestError: Error { 4 | case some 5 | case another 6 | case etc 7 | } 8 | 9 | extension TestError: Equatable {} 10 | -------------------------------------------------------------------------------- /Tests/SwiftlaneTests/DITests.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import SwiftlaneCore 4 | import XCTest 5 | 6 | @testable import Swiftlane 7 | 8 | final class DITests: XCTestCase { 9 | func testAllDIObjects() { 10 | DependencyResolver.shared.register(Logging.self) { 11 | DetailedLogger(logLevel: .verbose) 12 | } 13 | DependenciesFactory.registerProducers() 14 | DependencyResolver.shared.register(EnvironmentValueReading.self) { 15 | struct Fake: ProcessInfoProtocol { 16 | var environment: [String: String] = [ 17 | "GITLAB_API_ENDPOINT": "https://127.0.0.1", 18 | "PROJECT_ACCESS_TOKEN": "PROJECT_ACCESS_TOKEN", 19 | "JIRA_API_TOKEN": "JIRA_API_TOKEN", 20 | "JIRA_API_ENDPOINT": "https://127.0.0.1", 21 | "GIT_AUTHOR_EMAIL": "GIT_AUTHOR_EMAIL", 22 | "GITLAB_GROUP_DEV_TEAM_ID_TO_FETCH_MEMBERS": "123123", 23 | "ADP_ARTIFACTS_REPO": "https://127.0.0.1", 24 | ] 25 | var arguments: [String] = [] 26 | var hostName: String = "host" 27 | var processName: String = "processName" 28 | var processIdentifier: Int32 = 123 29 | var globallyUniqueString: String = "globallyUniqueString" 30 | } 31 | return EnvironmentValueReader(processInfo: Fake()) 32 | } 33 | 34 | for key in DependencyResolver.shared.allRegisteredTypes() { 35 | print(key) 36 | let _: Any = DependencyResolver.shared.resolve(key, .shared) 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Tests/SwiftlaneTests/IssueKeyParserTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | @testable import Swiftlane 4 | import XCTest 5 | 6 | final class IssueKeyParserTests: XCTestCase { 7 | func test_parsingCommentInGitLab() throws { 8 | let parser = JiraIssueKeyParser(jiraProjectKey: "ABCD") 9 | 10 | try XCTAssertEqual( 11 | parser.parse(from: "ABCD-0000 test"), 12 | ["ABCD-0000"] 13 | ) 14 | try XCTAssertEqual( 15 | parser.parse(from: "ABCD-1234 test"), 16 | ["ABCD-1234"] 17 | ) 18 | try XCTAssertEqual( 19 | parser.parse(from: "ABCD-12 test"), 20 | ["ABCD-12"] 21 | ) 22 | try XCTAssertEqual( 23 | parser.parse(from: "ABCD- test"), 24 | [] 25 | ) 26 | try XCTAssertEqual( 27 | parser.parse(from: "ABCD-0000"), 28 | ["ABCD-0000"] 29 | ) 30 | try XCTAssertEqual( 31 | parser.parse(from: "APNI-0000 Tests"), 32 | [] 33 | ) 34 | try XCTAssertEqual( 35 | parser.parse(from: "ABCD-34,ABCD-22, (ABCD-5),,,,AABCD-4, ABCD-1 ABCD-1 ABCD-3"), 36 | ["ABCD-1", "ABCD-22", "ABCD-3", "ABCD-34", "ABCD-5"] 37 | ) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Tests/SwiftlaneTests/Services/RangesWelderTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Foundation 4 | import XCTest 5 | 6 | @testable import Swiftlane 7 | 8 | class RangesWelderTests: XCTestCase { 9 | var welder: RangesWelder! 10 | 11 | override func setUp() { 12 | super.setUp() 13 | 14 | welder = .init() 15 | } 16 | 17 | override func tearDown() { 18 | super.tearDown() 19 | 20 | welder = nil 21 | } 22 | 23 | func test_weldRanges() { 24 | XCTAssertEqual(welder.weldRanges( 25 | from: [1, 2, 3, 4, 5] 26 | ), ["1...5"]) 27 | 28 | XCTAssertEqual(welder.weldRanges( 29 | from: [10] 30 | ), ["10"]) 31 | 32 | XCTAssertEqual(welder.weldRanges( 33 | from: [1, 2, 4, 5] 34 | ), ["1...2", "4...5"]) 35 | 36 | XCTAssertEqual(welder.weldRanges( 37 | from: [1, 3, 4, 5] 38 | ), ["1", "3...5"]) 39 | 40 | XCTAssertEqual(welder.weldRanges( 41 | from: [1, 3, 5] 42 | ), ["1", "3", "5"]) 43 | 44 | XCTAssertEqual(welder.weldRanges( 45 | from: [1, 2, 3, 4, 5, 100, 200] 46 | ), ["1...5", "100", "200"]) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Tests/SwiftlaneTests/Services/Stubs.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Foundation 4 | import SwiftlaneCore 5 | import SwiftlaneUnitTestTools 6 | 7 | @testable import Swiftlane 8 | 9 | extension ParsedExpiringToDoModel { 10 | static func random( 11 | file: RelativePath = .random(), 12 | line: UInt = .random(in: 0 ... 10_000_000), 13 | fullMatch: String = .random(), 14 | author: String? = .random(), 15 | dateString: String = .random() 16 | ) -> Self { 17 | ParsedExpiringToDoModel( 18 | file: file, 19 | line: line, 20 | fullMatch: fullMatch, 21 | author: author, 22 | dateString: dateString 23 | ) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Tests/SwiftlaneTests/Stubs/SpacesIndentedFile.txt: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Foundation 4 | 5 | /// Spaces indented file 6 | class Foo { 7 | func foo() { 8 | print("gogo spaces") 9 | } 10 | 11 | func bar() { 12 | print("gogo spaces") 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Tests/SwiftlaneTests/Stubs/TabsIndentedFile.txt: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Foundation 4 | 5 | /// Tabs indented file 6 | class Foo { 7 | func foo() { 8 | print("gogo tabs") 9 | } 10 | 11 | func bar() { 12 | print("gogo tabs") 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Tests/XcodebuildTests/Stubs/TestPlanWithEnvVariables.json: -------------------------------------------------------------------------------- 1 | { 2 | "RAnDOmKey": [ 3 | 1,2,3,4 4 | ], 5 | "configurations" : [ 6 | { 7 | "id" : "233FEB36-02C6-4739-A9F1-AAB9077C6BEB", 8 | "name" : "Configuration 1", 9 | "options" : { 10 | 11 | } 12 | } 13 | ], 14 | "defaultOptions" : { 15 | "environmentVariableEntries" : [ 16 | { 17 | "key" : "tp_var1", 18 | "value" : "OLD VALUE 1" 19 | }, 20 | { 21 | "key" : "1", 22 | "value" : "2" 23 | }, 24 | { 25 | "key" : "3", 26 | "value" : "4" 27 | } 28 | ], 29 | "maximumTestRepetitions" : 2, 30 | "testRepetitionMode" : "retryOnFailure" 31 | }, 32 | "testTargets" : [ 33 | { 34 | "parallelizable" : true, 35 | "selectedTests" : [ 36 | "PickupTest\/testCheckAlcoholAgeInfoInFavouriteAndOrder()", 37 | "PickupTest\/testCheckAlcoholAgeNotConfirmedAndCancelOrderNotAlcoholShip()", 38 | "PickupTest\/testCheckPickupActions()" 39 | ], 40 | "target" : { 41 | "containerPath" : "container:Project.xcodeproj", 42 | "identifier" : "BD0E5B56E34A361611FCFB41", 43 | "name" : "SOMERANDOMUITests" 44 | } 45 | } 46 | ], 47 | "version" : 1 48 | } 49 | -------------------------------------------------------------------------------- /Tests/XcodebuildTests/Stubs/TestPlanWithEnvVariablesPatched.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations" : [ 3 | { 4 | "id" : "233FEB36-02C6-4739-A9F1-AAB9077C6BEB", 5 | "name" : "Configuration 1", 6 | "options" : { 7 | 8 | } 9 | } 10 | ], 11 | "defaultOptions" : { 12 | "environmentVariableEntries" : [ 13 | { 14 | "key" : "tp_var_2", 15 | "value" : "value 2" 16 | }, 17 | { 18 | "key" : "tp_var1", 19 | "value" : "value1" 20 | }, 21 | { 22 | "key" : "3", 23 | "value" : "4" 24 | }, 25 | { 26 | "key" : "1", 27 | "value" : "2" 28 | } 29 | ], 30 | "maximumTestRepetitions" : 2, 31 | "testRepetitionMode" : "retryOnFailure" 32 | }, 33 | "RAnDOmKey" : [ 34 | 1, 35 | 2, 36 | 3, 37 | 4 38 | ], 39 | "testTargets" : [ 40 | { 41 | "parallelizable" : true, 42 | "selectedTests" : [ 43 | "PickupTest\/testCheckAlcoholAgeInfoInFavouriteAndOrder()", 44 | "PickupTest\/testCheckAlcoholAgeNotConfirmedAndCancelOrderNotAlcoholShip()", 45 | "PickupTest\/testCheckPickupActions()" 46 | ], 47 | "target" : { 48 | "containerPath" : "container:Project.xcodeproj", 49 | "identifier" : "BD0E5B56E34A361611FCFB41", 50 | "name" : "SOMERANDOMUITests" 51 | } 52 | } 53 | ], 54 | "version" : 1 55 | } -------------------------------------------------------------------------------- /Tests/XcodebuildTests/Stubs/TestPlanWithSelectedTests.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations" : [ 3 | { 4 | "id" : "233FEB36-02C6-4739-A9F1-AAB9077C6BEB", 5 | "name" : "Configuration 1", 6 | "options" : { 7 | 8 | } 9 | } 10 | ], 11 | "defaultOptions" : { 12 | 13 | }, 14 | "testTargets" : [ 15 | { 16 | "parallelizable" : true, 17 | "selectedTests" : [ 18 | "PickupTest\/testCheckAlcoholAgeInfoInFavouriteAndOrder()", 19 | "PickupTest\/testCheckAlcoholAgeNotConfirmedAndCancelOrderNotAlcoholShip()", 20 | "PickupTest\/testCheckPickupActions()" 21 | ], 22 | "target" : { 23 | "containerPath" : "container:Project.xcodeproj", 24 | "identifier" : "BD0E5B56E34A361611FCFB41", 25 | "name" : "SOMERANDOMUITests" 26 | } 27 | } 28 | ], 29 | "version" : 1 30 | } 31 | -------------------------------------------------------------------------------- /Tests/XcodebuildTests/Stubs/TestPlanWithSkippedTests.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations" : [ 3 | { 4 | "id" : "233FEB36-02C6-4739-A9F1-AAB9077C6BEB", 5 | "name" : "Configuration 1", 6 | "options" : { 7 | 8 | } 9 | } 10 | ], 11 | "defaultOptions" : { 12 | 13 | }, 14 | "testTargets" : [ 15 | { 16 | "parallelizable" : true, 17 | "skippedTests" : [ 18 | "AdditionalOrderTest\/testCheckAdditionalOrderCatalogActions()", 19 | "AdditionalOrderTest\/testCheckAdditionalOrderIsNotPossible()", 20 | ], 21 | "target" : { 22 | "containerPath" : "container:Project.xcodeproj", 23 | "identifier" : "BD0E5B56E34A361611FCFB41", 24 | "name" : "UITests" 25 | } 26 | } 27 | ], 28 | "version" : 1 29 | } 30 | -------------------------------------------------------------------------------- /Tests/XcodebuildTests/Stubs/TestPlanWithoutEnvVariables.json: -------------------------------------------------------------------------------- 1 | { 2 | "RAnDOmKey": [ 3 | 1,2,3,4 4 | ], 5 | "configurations" : [ 6 | { 7 | "id" : "233FEB36-02C6-4739-A9F1-AAB9077C6BEB", 8 | "name" : "Configuration 1", 9 | "options" : { 10 | 11 | } 12 | } 13 | ], 14 | "defaultOptions" : { 15 | "maximumTestRepetitions" : 2, 16 | "testRepetitionMode" : "retryOnFailure" 17 | }, 18 | "testTargets" : [ 19 | { 20 | "parallelizable" : true, 21 | "selectedTests" : [ 22 | "PickupTest\/testCheckAlcoholAgeInfoInFavouriteAndOrder()", 23 | "PickupTest\/testCheckAlcoholAgeNotConfirmedAndCancelOrderNotAlcoholShip()", 24 | "PickupTest\/testCheckPickupActions()" 25 | ], 26 | "target" : { 27 | "containerPath" : "container:Project.xcodeproj", 28 | "identifier" : "BD0E5B56E34A361611FCFB41", 29 | "name" : "SOMERANDOMUITests" 30 | } 31 | } 32 | ], 33 | "version" : 1 34 | } 35 | -------------------------------------------------------------------------------- /Tests/XcodebuildTests/Stubs/TestPlanWithoutEnvVariablesPatched.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations" : [ 3 | { 4 | "id" : "233FEB36-02C6-4739-A9F1-AAB9077C6BEB", 5 | "name" : "Configuration 1", 6 | "options" : { 7 | 8 | } 9 | } 10 | ], 11 | "defaultOptions" : { 12 | "environmentVariableEntries" : [ 13 | { 14 | "key" : "tp_var_2", 15 | "value" : "value 2" 16 | }, 17 | { 18 | "key" : "tp_var1", 19 | "value" : "value1" 20 | } 21 | ], 22 | "maximumTestRepetitions" : 2, 23 | "testRepetitionMode" : "retryOnFailure" 24 | }, 25 | "RAnDOmKey" : [ 26 | 1, 27 | 2, 28 | 3, 29 | 4 30 | ], 31 | "testTargets" : [ 32 | { 33 | "parallelizable" : true, 34 | "selectedTests" : [ 35 | "PickupTest\/testCheckAlcoholAgeInfoInFavouriteAndOrder()", 36 | "PickupTest\/testCheckAlcoholAgeNotConfirmedAndCancelOrderNotAlcoholShip()", 37 | "PickupTest\/testCheckPickupActions()" 38 | ], 39 | "target" : { 40 | "containerPath" : "container:Project.xcodeproj", 41 | "identifier" : "BD0E5B56E34A361611FCFB41", 42 | "name" : "SOMERANDOMUITests" 43 | } 44 | } 45 | ], 46 | "version" : 1 47 | } -------------------------------------------------------------------------------- /Tests/XcodebuildTests/XCTestRunFinderTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Foundation 4 | import SwiftlaneCore 5 | import SwiftlaneCoreMocks 6 | import XCTest 7 | 8 | @testable import Xcodebuild 9 | 10 | class XCTestRunFinderTests: XCTestCase { 11 | var finder: XCTestRunFinder! 12 | 13 | var filesManager: FSManagingMock! 14 | 15 | override func setUp() { 16 | super.setUp() 17 | 18 | filesManager = FSManagingMock() 19 | 20 | finder = XCTestRunFinder( 21 | filesManager: filesManager 22 | ) 23 | } 24 | 25 | override func tearDown() { 26 | filesManager = nil 27 | 28 | super.tearDown() 29 | } 30 | 31 | func test_findXCTestRunFile() throws { 32 | // given 33 | let derivedDataPath = AbsolutePath.random() 34 | let xctestRunName = AbsolutePath.random(suffix: ".xctestrun") 35 | let subpaths = [AbsolutePath.random(), AbsolutePath.random(), xctestRunName] 36 | 37 | filesManager.given( 38 | .find( 39 | .value(derivedDataPath), 40 | file: .any, 41 | line: .any, 42 | willReturn: subpaths 43 | ) 44 | ) 45 | 46 | // when 47 | let result = try finder.findXCTestRunFile(derivedDataPath: derivedDataPath) 48 | 49 | // then 50 | XCTAssertEqual(result, xctestRunName) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /bootstrap: -------------------------------------------------------------------------------- 1 | ./Scripts/bootstrap_project.sh -------------------------------------------------------------------------------- /format_code: -------------------------------------------------------------------------------- 1 | Scripts/format_code.sh --------------------------------------------------------------------------------