diff --git a/.gitignore b/.gitignore index 5602df3..9ed6d32 100644 --- a/.gitignore +++ b/.gitignore @@ -7,4 +7,5 @@ DerivedData/ .swiftpm/config/registries.json .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata .netrc -.swiftpm \ No newline at end of file +.swiftpm +*.json diff --git a/Sources/copilot-action/CopilotAction.swift b/Sources/copilot-action/CopilotAction.swift index d2d8f10..6e15d52 100644 --- a/Sources/copilot-action/CopilotAction.swift +++ b/Sources/copilot-action/CopilotAction.swift @@ -1,4 +1,6 @@ +import APIBuilder import ArgumentParser +import Foundation @main struct CopilotAction: AsyncParsableCommand { @@ -7,11 +9,34 @@ struct CopilotAction: AsyncParsableCommand { abstract: "Github Action main entry point for copilot-ios", subcommands: [ PRSizeLabeler.self, - ], - defaultSubcommand: PRSizeLabeler.self + IssueChecker.self, + ] ) +} + +extension ParsableCommand { + func getEnv(_ key: String, defaultValue: String? = nil) throws -> String { + guard let value = ProcessInfo.processInfo.environment[key] ?? defaultValue else { + throw StringError("\(key) environment variable not set") + } + + return value + } + + func getInputEnv(_ key: String, defaultValue: String? = nil) throws -> String { + guard let value = ProcessInfo.processInfo.environment["INPUT_" + key] ?? defaultValue else { + throw StringError("\(key) environment variable not set") + } + + return value + } + + func getInputEnv(_ key: String, defaultValue: Int? = nil) throws -> Int { + let envValue = ProcessInfo.processInfo.environment["INPUT_" + key] + guard let value = envValue.flatMap({ Int($0) }) ?? defaultValue else { + throw StringError("\(key) environment variable not set") + } - func run() async throws { - print("hello world!") + return value } } diff --git a/Sources/copilot-action/GithubConfiguration.swift b/Sources/copilot-action/GithubConfiguration.swift new file mode 100644 index 0000000..6003d2f --- /dev/null +++ b/Sources/copilot-action/GithubConfiguration.swift @@ -0,0 +1,19 @@ +import APIBuilder +import Foundation + +struct GithubConfiguration: APIConfiguration { + let host = URL(string: "https://api.github.com")! + + var requestHeaders: [String : String] { + [ + "Content-Type": "application/vnd.github.v3+json", + "Authorization": "Bearer \(token)", + ] + } + + let token: String + + init(token: String) { + self.token = token + } +} diff --git a/Sources/copilot-action/IssueChecker.swift b/Sources/copilot-action/IssueChecker.swift new file mode 100644 index 0000000..8d2cad2 --- /dev/null +++ b/Sources/copilot-action/IssueChecker.swift @@ -0,0 +1,61 @@ +import APIBuilder +import ArgumentParser +import Foundation + +// MARK: - API Types + +fileprivate struct PullRequestEvent: Codable { + fileprivate struct PullRequest: Codable { + fileprivate struct Head: Codable { + let ref: String + } + let body: String + let title: String + let head: Head + } + let pull_request: PullRequest +} + +struct IssueChecker: AsyncParsableCommand { + static var configuration = CommandConfiguration( + commandName: "issue-checker" + ) + + func run() async throws { + let eventPath = try getEnv("GITHUB_EVENT_PATH") + + guard let eventData = try String(contentsOfFile: eventPath).data(using: .utf8) else { + throw StringError("could not load event data at \(eventPath)") + } + + let pullRequestEvent = try JSONDecoder().decode(PullRequestEvent.self, from: eventData) + + print(pullRequestEvent.pull_request.body) + print(pullRequestEvent.pull_request.title) + print(pullRequestEvent.pull_request.head.ref) + + let issuePrefix = try getInputEnv("ISSUE_CHECKER_PREFIX") as String + + let inputsToCheck = [ + pullRequestEvent.pull_request.body, + pullRequestEvent.pull_request.title, + pullRequestEvent.pull_request.head.ref, + ] + + for input in inputsToCheck { + let range = input + .lowercased() + .range( + of: "\(issuePrefix.lowercased())\\d{1,}", + options: .regularExpression + ) + + if let range = range { + print("Found \(input[range])") + return + } + } + + throw StringError("Could not find issue in the PR") + } +} diff --git a/Sources/copilot-action/PRSizeLabeler.swift b/Sources/copilot-action/PRSizeLabeler.swift index 8fd4c8d..257a066 100644 --- a/Sources/copilot-action/PRSizeLabeler.swift +++ b/Sources/copilot-action/PRSizeLabeler.swift @@ -2,12 +2,14 @@ import ArgumentParser import APIBuilder import Foundation -struct PullRequest: Codable { - let additions: Int - let deletions: Int -} +// MARK: - API Types + +fileprivate struct PullRequestEvent: Codable { + fileprivate struct PullRequest: Codable { + let additions: Int + let deletions: Int + } -struct PullRequestEvent: Codable { let pull_request: PullRequest let number: Int } @@ -16,22 +18,7 @@ struct LabelsChangeRequest: Codable { let labels: [String] } -struct GithubConfiguration: APIConfiguration { - let host = URL(string: "https://api.github.com")! - - var requestHeaders: [String : String] { - [ - "Content-Type": "application/vnd.github.v3+json", - "Authorization": "Bearer \(token)", - ] - } - - let token: String - - init(token: String) { - self.token = token - } -} +// MARK: - Endpoints extension APIEndpoint { static func setLabels(repo: String, pullRequestID: Int) -> Self { @@ -48,9 +35,9 @@ struct PRSizeLabeler: AsyncParsableCommand { ) func run() async throws { - let githubToken = try getEnv(key: "GITHUB_TOKEN") - let repo = try getEnv(key: "GITHUB_REPOSITORY") - let eventPath = try getEnv(key: "GITHUB_EVENT_PATH") + let githubToken = try getEnv("GITHUB_TOKEN") + let repo = try getEnv("GITHUB_REPOSITORY") + let eventPath = try getEnv("GITHUB_EVENT_PATH") guard let eventData = try String(contentsOfFile: eventPath).data(using: .utf8) else { throw StringError("could not load event data at \(eventPath)") @@ -58,38 +45,31 @@ struct PRSizeLabeler: AsyncParsableCommand { let pullRequestEvent = try JSONDecoder().decode(PullRequestEvent.self, from: eventData) - let totalLinesChanged = pullRequestEvent.pull_request.additions + pullRequestEvent.pull_request.deletions - print("The pull has \(totalLinesChanged) changed lines") + let totalLinesChanged = pullRequestEvent.pull_request.additions + + pullRequestEvent.pull_request.deletions + + print("The pull request has \(totalLinesChanged) changed lines") let label: String - if totalLinesChanged < 10 { - label = "XS" - } else if totalLinesChanged < 100 { - label = "S" - } else if totalLinesChanged < 500 { - label = "M" - } else if totalLinesChanged < 1000 { - label = "L" + if try totalLinesChanged < getInputEnv("PR_SIZE_XS_LIMIT", defaultValue: 10) { + label = try getInputEnv("PR_SIZE_XS_LABEL", defaultValue: "XS") + } else if try totalLinesChanged < getInputEnv("PR_SIZE_S_LIMIT", defaultValue: 100) { + label = try getInputEnv("PR_SIZE_S_LABEL", defaultValue: "S") + } else if try totalLinesChanged < getInputEnv("PR_SIZE_M_LIMIT", defaultValue: 500) { + label = try getInputEnv("PR_SIZE_M_LABEL", defaultValue: "M") + } else if try totalLinesChanged < getInputEnv("PR_SIZE_L_LIMIT", defaultValue: 1000) { + label = try getInputEnv("PR_SIZE_L_LABEL", defaultValue: "L") } else { - label = "XL" + label = try getInputEnv("PR_SIZE_XL_LABEL", defaultValue: "XL") } - print("Assigning the \(label) label") + print("Assigning the \(label) label to pull request #\(pullRequestEvent.number)") let provider = APIProvider(configuration: GithubConfiguration(token: githubToken)) - let body = LabelsChangeRequest(labels: [label]) try await provider.request( .setLabels(repo: repo, pullRequestID: pullRequestEvent.number), - body: body + body: LabelsChangeRequest(labels: [label]) ) } - - private func getEnv(key: String) throws -> String { - guard let value = ProcessInfo.processInfo.environment[key] else { - throw StringError("\(key) environment variable not set") - } - - return value - } } diff --git a/action.yml b/action.yml index cbb3ca4..4ab1a3c 100644 --- a/action.yml +++ b/action.yml @@ -1,7 +1,12 @@ name: Copilot Swift PR Actions description: Collection of PR Actions geared mostly towards iOS projects - +inputs: + action_name: + description: 'Which action should be invoked' + required: true runs: using: docker image: Dockerfile + args: + - ${{ inputs.action_name }}