import { GITHUB_EMPTY_RESPONSE_SCHEMA, GITHUB_PULL_REQUEST_BRANCH_UPDATE_SCHEMA, GITHUB_PULL_REQUEST_FILE_SCHEMA, GITHUB_PULL_REQUEST_REVIEW_COMMENT_SCHEMA, GITHUB_PULL_REQUEST_REVIEW_REQUEST_SCHEMA, GITHUB_PULL_REQUEST_REVIEW_RESOURCE_SCHEMA, } from "@automate.ax/integration-contracts/github" import * as z from "zod" import { defineAction } from "../../../automation/actions" import { getGitHubApi } from "../lib/api" import { GITHUB_PAGE_INPUT_SCHEMA, GITHUB_PULL_REQUEST_NUMBER_INPUT_SCHEMA, GITHUB_REPOSITORY_INPUT_SCHEMA, } from "../lib/input-schemas" import { GITHUB_PULL_REQUEST_BRANCH_UPDATE_REQUIREMENT, GITHUB_PULL_REQUESTS_READ_SCOPE, GITHUB_PULL_REQUESTS_WRITE_SCOPE, } from "../lib/scopes" const GITHUB_PULL_REQUEST_REVIEW_COMMENT_TARGET_SCHEMA = z.union([ z.object({ /** Comment on the whole file instead of one diff line. */ subjectType: z.literal("file"), }), z .object({ /** Last line of the diff range. */ line: z.number().int().positive(), /** Side of the diff for `line`. */ side: z.enum(["LEFT", "RIGHT"]), /** Comment on one or more diff lines. */ subjectType: z.literal("line").prefault("line"), }) .and( z.union([ z.object({ /** First line of a multi-line range. */ startLine: z.number().int().positive(), /** Side of the diff for `startLine`. */ startSide: z.enum(["LEFT", "RIGHT"]), }), z.object({ startLine: z.undefined().optional(), startSide: z.undefined().optional(), }), ]), ), ]) /** Lists files changed by one GitHub pull request. */ export const listGitHubPullRequestFiles = defineAction( "List GitHub pull request files", ) .describe("Lists files changed by one pull request.") .account("github", GITHUB_PULL_REQUESTS_READ_SCOPE) .input( GITHUB_PULL_REQUEST_NUMBER_INPUT_SCHEMA.extend(GITHUB_PAGE_INPUT_SCHEMA), ) .output(GITHUB_PULL_REQUEST_FILE_SCHEMA.array()) .retry({ replaySafety: "safe" }) .handler(async ({ account, input }) => { const { data } = await getGitHubApi(account.secret).rest.pulls.listFiles({ owner: input.owner, page: input.page, per_page: input.perPage, pull_number: input.pullRequestNumber, repo: input.repository, }) return data }) /** Lists reviews submitted on one GitHub pull request. */ export const listGitHubPullRequestReviews = defineAction( "List GitHub pull request reviews", ) .describe("Lists reviews submitted on one pull request.") .account("github", GITHUB_PULL_REQUESTS_READ_SCOPE) .input( GITHUB_PULL_REQUEST_NUMBER_INPUT_SCHEMA.extend(GITHUB_PAGE_INPUT_SCHEMA), ) .output(GITHUB_PULL_REQUEST_REVIEW_RESOURCE_SCHEMA.array()) .retry({ replaySafety: "safe" }) .handler(async ({ account, input }) => { const { data } = await getGitHubApi(account.secret).rest.pulls.listReviews({ owner: input.owner, page: input.page, per_page: input.perPage, pull_number: input.pullRequestNumber, repo: input.repository, }) return data }) /** Requests user or team reviews on a GitHub pull request. */ export const requestGitHubPullRequestReviewers = defineAction( "Request GitHub pull request reviewers", ) .describe("Requests reviews from users or teams on one pull request.") .account("github", GITHUB_PULL_REQUESTS_WRITE_SCOPE) .input( GITHUB_PULL_REQUEST_NUMBER_INPUT_SCHEMA.extend({ /** User logins to request. */ reviewers: z.string().min(1).array().prefault([]), /** Organization team slugs to request. */ teamReviewers: z.string().min(1).array().optional(), }), ) .output(GITHUB_PULL_REQUEST_REVIEW_REQUEST_SCHEMA) .retry({ replaySafety: "unsafe" }) .handler(async ({ account, input }) => { const { data } = await getGitHubApi( account.secret, ).rest.pulls.requestReviewers({ owner: input.owner, pull_number: input.pullRequestNumber, repo: input.repository, reviewers: input.reviewers, ...(input.teamReviewers && { team_reviewers: input.teamReviewers }), }) return data }) /** Removes requested user or team reviewers from a GitHub pull request. */ export const removeGitHubPullRequestReviewers = defineAction( "Remove GitHub pull request reviewers", ) .describe("Removes requested user or team reviewers from one pull request.") .account("github", GITHUB_PULL_REQUESTS_WRITE_SCOPE) .input( GITHUB_PULL_REQUEST_NUMBER_INPUT_SCHEMA.extend({ /** User logins to remove. */ reviewers: z.string().min(1).array().prefault([]), /** Organization team slugs to remove. */ teamReviewers: z.string().min(1).array().optional(), }), ) .output(GITHUB_PULL_REQUEST_REVIEW_REQUEST_SCHEMA) .retry({ replaySafety: "unsafe" }) .handler(async ({ account, input }) => { const { data } = await getGitHubApi( account.secret, ).rest.pulls.removeRequestedReviewers({ owner: input.owner, pull_number: input.pullRequestNumber, repo: input.repository, reviewers: input.reviewers, ...(input.teamReviewers && { team_reviewers: input.teamReviewers }), }) return data }) /** Updates a pull request branch with changes from its base branch. */ export const updateGitHubPullRequestBranch = defineAction( "Update GitHub pull request branch", ) .describe("Updates a pull request head branch with its base branch.") .account("github", GITHUB_PULL_REQUEST_BRANCH_UPDATE_REQUIREMENT) .input( GITHUB_PULL_REQUEST_NUMBER_INPUT_SCHEMA.extend({ /** Expected current head SHA. */ expectedHeadSha: z.string().min(1).optional(), }), ) .output(GITHUB_PULL_REQUEST_BRANCH_UPDATE_SCHEMA) .retry({ replaySafety: "unsafe" }) .handler(async ({ account, input }) => { const { data } = await getGitHubApi(account.secret).rest.pulls.updateBranch( { owner: input.owner, pull_number: input.pullRequestNumber, repo: input.repository, ...(input.expectedHeadSha && { expected_head_sha: input.expectedHeadSha, }), }, ) return data }) /** Lists inline review comments on one GitHub pull request. */ export const listGitHubPullRequestReviewComments = defineAction( "List GitHub pull request review comments", ) .describe("Lists inline review comments on one pull request.") .account("github", GITHUB_PULL_REQUESTS_READ_SCOPE) .input( GITHUB_PULL_REQUEST_NUMBER_INPUT_SCHEMA.extend({ ...GITHUB_PAGE_INPUT_SCHEMA, /** Sort direction. */ direction: z.enum(["asc", "desc"]).optional(), /** Only return comments updated after this timestamp. */ since: z.iso.datetime({ offset: true }).optional(), /** Review-comment sort field. */ sort: z.enum(["created", "updated"]).optional(), }), ) .output(GITHUB_PULL_REQUEST_REVIEW_COMMENT_SCHEMA.array()) .retry({ replaySafety: "safe" }) .handler(async ({ account, input }) => { const { data } = await getGitHubApi( account.secret, ).rest.pulls.listReviewComments({ owner: input.owner, page: input.page, per_page: input.perPage, pull_number: input.pullRequestNumber, repo: input.repository, ...(input.direction && { direction: input.direction }), ...(input.since && { since: input.since }), ...(input.sort && { sort: input.sort }), }) return data }) /** Gets one inline GitHub pull request review comment. */ export const getGitHubPullRequestReviewComment = defineAction( "Get GitHub pull request review comment", ) .describe("Gets one inline pull request review comment by ID.") .account("github", GITHUB_PULL_REQUESTS_READ_SCOPE) .input( GITHUB_REPOSITORY_INPUT_SCHEMA.extend({ /** Repository-scoped review comment ID. */ commentId: z.number().int().positive(), }), ) .output(GITHUB_PULL_REQUEST_REVIEW_COMMENT_SCHEMA) .retry({ replaySafety: "safe" }) .handler(async ({ account, input }) => { const { data } = await getGitHubApi( account.secret, ).rest.pulls.getReviewComment({ comment_id: input.commentId, owner: input.owner, repo: input.repository, }) return data }) /** Creates an inline review comment on a pull request diff. */ export const createGitHubPullRequestReviewComment = defineAction( "Create GitHub pull request review comment", ) .describe("Creates an inline review comment on a pull request diff.") .account("github", GITHUB_PULL_REQUESTS_WRITE_SCOPE) .input( GITHUB_PULL_REQUEST_NUMBER_INPUT_SCHEMA.and( z .object({ /** Markdown comment body. */ body: z.string().min(1), /** Commit SHA being reviewed. */ commitId: z.string().min(1), /** Repository-relative file path. */ path: z.string().min(1), }) .and(GITHUB_PULL_REQUEST_REVIEW_COMMENT_TARGET_SCHEMA), ), ) .output(GITHUB_PULL_REQUEST_REVIEW_COMMENT_SCHEMA) .retry({ replaySafety: "unsafe" }) .handler(async ({ account, input }) => { const { data } = await getGitHubApi( account.secret, ).rest.pulls.createReviewComment({ body: input.body, commit_id: input.commitId, owner: input.owner, path: input.path, pull_number: input.pullRequestNumber, repo: input.repository, subject_type: input.subjectType, ...("line" in input && { line: input.line, side: input.side, ...(input.startLine !== undefined && { start_line: input.startLine, start_side: input.startSide, }), }), }) return data }) /** Replies to an existing inline pull request review comment. */ export const replyToGitHubPullRequestReviewComment = defineAction( "Reply to GitHub pull request review comment", ) .describe("Replies to an existing inline pull request review comment.") .account("github", GITHUB_PULL_REQUESTS_WRITE_SCOPE) .input( GITHUB_PULL_REQUEST_NUMBER_INPUT_SCHEMA.extend({ /** Markdown reply body. */ body: z.string().min(1), /** Review comment ID being replied to. */ commentId: z.number().int().positive(), }), ) .output(GITHUB_PULL_REQUEST_REVIEW_COMMENT_SCHEMA) .retry({ replaySafety: "unsafe" }) .handler(async ({ account, input }) => { const { data } = await getGitHubApi( account.secret, ).rest.pulls.createReplyForReviewComment({ body: input.body, comment_id: input.commentId, owner: input.owner, pull_number: input.pullRequestNumber, repo: input.repository, }) return data }) /** Updates one inline GitHub pull request review comment. */ export const updateGitHubPullRequestReviewComment = defineAction( "Update GitHub pull request review comment", ) .describe("Replaces an inline pull request review comment body.") .account("github", GITHUB_PULL_REQUESTS_WRITE_SCOPE) .input( GITHUB_REPOSITORY_INPUT_SCHEMA.extend({ /** Replacement Markdown body. */ body: z.string().min(1), /** Repository-scoped review comment ID. */ commentId: z.number().int().positive(), }), ) .output(GITHUB_PULL_REQUEST_REVIEW_COMMENT_SCHEMA) .retry({ replaySafety: "unsafe" }) .handler(async ({ account, input }) => { const { data } = await getGitHubApi( account.secret, ).rest.pulls.updateReviewComment({ body: input.body, comment_id: input.commentId, owner: input.owner, repo: input.repository, }) return data }) /** Deletes one inline GitHub pull request review comment. */ export const deleteGitHubPullRequestReviewComment = defineAction( "Delete GitHub pull request review comment", ) .describe("Deletes one inline pull request review comment by ID.") .account("github", GITHUB_PULL_REQUESTS_WRITE_SCOPE) .input( GITHUB_REPOSITORY_INPUT_SCHEMA.extend({ /** Repository-scoped review comment ID. */ commentId: z.number().int().positive(), }), ) .output(GITHUB_EMPTY_RESPONSE_SCHEMA) .retry({ replaySafety: "unsafe" }) .handler(async ({ account, input }) => { await getGitHubApi(account.secret).rest.pulls.deleteReviewComment({ comment_id: input.commentId, owner: input.owner, repo: input.repository, }) return {} })