diff --git a/examples/github-webhook/src/index.ts b/examples/github-webhook/src/index.ts index 20c014acd..b9d7745c9 100644 --- a/examples/github-webhook/src/index.ts +++ b/examples/github-webhook/src/index.ts @@ -18,12 +18,12 @@ new Trigger({ }).listen(); new Trigger({ - id: "github-webhook-issue_comment", - name: "GitHub issue comment: triggerdotdev/trigger.dev-examples", + id: "github-webhook-pull_request", + name: "GitHub PR: triggerdotdev/trigger.dev-examples", apiKey: "trigger_dev_zC25mKNn6c0q", endpoint: "ws://localhost:8889/ws", logLevel: "debug", - on: github.events.issueCommentEvent({ + on: github.events.pullRequestEvent({ repo: "triggerdotdev/trigger.dev-examples", }), run: async (event, ctx) => { diff --git a/packages/trigger-integrations/src/integrations/github/events.ts b/packages/trigger-integrations/src/integrations/github/events.ts index f9ca0a0a4..3a23b1e10 100644 --- a/packages/trigger-integrations/src/integrations/github/events.ts +++ b/packages/trigger-integrations/src/integrations/github/events.ts @@ -56,3 +56,31 @@ export function issueCommentEvent(params: { schema: github.schemas.issuesComments.issueCommentEventSchema, }; } + +export function pullRequestEvent(params: { + repo: string; +}): TriggerEvent { + return { + metadata: { + type: "WEBHOOK", + service: "github", + name: "pull_request", + filter: { + service: ["github"], + payload: { + repository: { + full_name: [params.repo], + }, + }, + event: ["pull_request"], + }, + source: github.schemas.WebhookSourceSchema.parse({ + subresource: "repository", + scopes: ["repo"], + repo: params.repo, + events: ["pull_request"], + }), + }, + schema: github.schemas.pullRequest.pullRequestEventSchema, + }; +} diff --git a/packages/trigger-providers/src/providers/github/schemas/index.ts b/packages/trigger-providers/src/providers/github/schemas/index.ts index 9994968f1..21f29812e 100644 --- a/packages/trigger-providers/src/providers/github/schemas/index.ts +++ b/packages/trigger-providers/src/providers/github/schemas/index.ts @@ -1,6 +1,7 @@ import { z } from "zod"; import * as issues from "./issues"; import * as issuesComments from "./issueComments"; +import * as pullRequest from "./pullRequests"; export const WebhookRepoSourceSchema = z.object({ subresource: z.literal("repository"), @@ -21,4 +22,4 @@ export const WebhookSourceSchema = z.union([ WebhookOrganizationSourceSchema, ]); -export { issues, issuesComments }; +export { issues, issuesComments, pullRequest }; diff --git a/packages/trigger-providers/src/providers/github/schemas/issueComments.ts b/packages/trigger-providers/src/providers/github/schemas/issueComments.ts index c19bfe134..8be801aad 100644 --- a/packages/trigger-providers/src/providers/github/schemas/issueComments.ts +++ b/packages/trigger-providers/src/providers/github/schemas/issueComments.ts @@ -84,7 +84,7 @@ export const issueCommentEditedEventSchema = z.object({ organization: organizationSchema.optional(), }); -export const issueCommentEventSchema = z.union([ +export const issueCommentEventSchema = z.discriminatedUnion("action", [ issueCommentCreatedEventSchema, issueCommentDeletedEventSchema, issueCommentEditedEventSchema, diff --git a/packages/trigger-providers/src/providers/github/schemas/pullRequests.ts b/packages/trigger-providers/src/providers/github/schemas/pullRequests.ts new file mode 100644 index 000000000..a55e85e81 --- /dev/null +++ b/packages/trigger-providers/src/providers/github/schemas/pullRequests.ts @@ -0,0 +1,411 @@ +import { z } from "zod"; +import { + authorAssociationSchema, + installationLiteSchema, + labelSchema, + linkSchema, + milestoneSchema, + organizationSchema, + repositorySchema, + teamSchema, + userSchema, +} from "./shared"; + +export const pullRequestAutoMergeSchema = z.object({ + enabled_by: userSchema, + merge_method: z.union([ + z.literal("merge"), + z.literal("squash"), + z.literal("rebase"), + ]), + commit_title: z.string(), + commit_message: z.string(), +}); + +export const pullRequestSchema = z.object({ + url: z.string(), + id: z.number(), + node_id: z.string(), + html_url: z.string(), + diff_url: z.string(), + patch_url: z.string(), + issue_url: z.string(), + number: z.number(), + state: z.union([z.literal("open"), z.literal("closed")]), + locked: z.boolean(), + title: z.string(), + user: userSchema, + body: z.string().nullable(), + created_at: z.string(), + updated_at: z.string(), + closed_at: z.string().nullable(), + merged_at: z.string().nullable(), + merge_commit_sha: z.string().nullable(), + assignee: userSchema.nullable(), + assignees: z.array(userSchema), + requested_reviewers: z.array(z.union([userSchema, teamSchema])), + requested_teams: z.array(teamSchema), + labels: z.array(labelSchema), + milestone: milestoneSchema.nullable(), + commits_url: z.string(), + review_comments_url: z.string(), + review_comment_url: z.string(), + comments_url: z.string(), + statuses_url: z.string(), + head: z.object({ + label: z.string(), + ref: z.string(), + sha: z.string(), + user: userSchema, + repo: repositorySchema, + }), + base: z.object({ + label: z.string(), + ref: z.string(), + sha: z.string(), + user: userSchema, + repo: repositorySchema, + }), + _links: z.object({ + self: linkSchema, + html: linkSchema, + issue: linkSchema, + comments: linkSchema, + review_comments: linkSchema, + review_comment: linkSchema, + commits: linkSchema, + statuses: linkSchema, + }), + author_association: authorAssociationSchema, + auto_merge: pullRequestAutoMergeSchema.nullable(), + active_lock_reason: z + .union([ + z.literal("resolved"), + z.literal("off-topic"), + z.literal("too heated"), + z.literal("spam"), + ]) + .nullable(), + draft: z.boolean(), + merged: z.boolean().nullable(), + mergeable: z.boolean().nullable(), + rebaseable: z.boolean().nullable(), + mergeable_state: z.string(), + merged_by: userSchema.nullable(), + comments: z.number(), + review_comments: z.number(), + maintainer_can_modify: z.boolean(), + commits: z.number(), + additions: z.number(), + deletions: z.number(), + changed_files: z.number(), +}); + +export const pullRequestAssignedEventSchema = z.object({ + action: z.literal("assigned"), + number: z.number(), + pull_request: pullRequestSchema, + assignee: userSchema, + repository: repositorySchema, + installation: installationLiteSchema.optional(), + organization: organizationSchema.optional(), + sender: userSchema, +}); + +export const pullRequestAutoMergeDisabledEventSchema = z.object({ + action: z.literal("auto_merge_disabled"), + number: z.number(), + pull_request: pullRequestSchema, + reason: z.string(), + repository: repositorySchema, + installation: installationLiteSchema.optional(), + organization: organizationSchema.optional(), + sender: userSchema, +}); + +export const pullRequestAutoMergeEnabledEventSchema = z.object({ + action: z.literal("auto_merge_enabled"), + number: z.number(), + pull_request: pullRequestSchema, + reason: z.string(), + repository: repositorySchema, + installation: installationLiteSchema.optional(), + organization: organizationSchema.optional(), + sender: userSchema, +}); + +export const pullRequestClosedEventSchema = z.object({ + action: z.literal("closed"), + number: z.number(), + pull_request: pullRequestSchema.and( + z.object({ + state: z.literal("closed"), + closed_at: z.string(), + merged: z.boolean(), + }) + ), + repository: repositorySchema, + installation: installationLiteSchema.optional(), + organization: organizationSchema.optional(), + sender: userSchema, +}); + +export const pullRequestConvertedToDraftEventSchema = z.object({ + action: z.literal("converted_to_draft"), + number: z.number(), + pull_request: pullRequestSchema.and( + z.object({ + closed_at: z.null(), + merged_at: z.null(), + draft: z.literal(true), + merged: z.literal(false), + merged_by: z.null(), + }) + ), + repository: repositorySchema, + installation: installationLiteSchema.optional(), + organization: organizationSchema.optional(), + sender: userSchema, +}); + +export const pullRequestDequeuedEventSchema = z.object({ + action: z.literal("dequeued"), + number: z.number(), + reason: z.string(), + pull_request: pullRequestSchema, + repository: repositorySchema, + installation: installationLiteSchema.optional(), + organization: organizationSchema.optional(), + sender: userSchema, +}); + +export const pullRequestEditedEventSchema = z.object({ + action: z.literal("edited"), + number: z.number(), + changes: z.object({ + body: z + .object({ + from: z.string(), + }) + .optional(), + title: z + .object({ + from: z.string(), + }) + .optional(), + base: z + .object({ + ref: z.object({ + from: z.string(), + }), + sha: z.object({ + from: z.string(), + }), + }) + .optional(), + }), + pull_request: pullRequestSchema, + repository: repositorySchema, + installation: installationLiteSchema.optional(), + organization: organizationSchema.optional(), + sender: userSchema, +}); + +export const pullRequestLabeledEventSchema = z.object({ + action: z.literal("labeled"), + number: z.number(), + pull_request: pullRequestSchema, + label: labelSchema, + repository: repositorySchema, + installation: installationLiteSchema.optional(), + organization: organizationSchema.optional(), + sender: userSchema, +}); + +export const pullRequestLockedEventSchema = z.object({ + action: z.literal("locked"), + number: z.number(), + pull_request: pullRequestSchema, + repository: repositorySchema, + installation: installationLiteSchema.optional(), + organization: organizationSchema.optional(), + sender: userSchema, +}); + +export const pullRequestOpenedEventSchema = z.object({ + action: z.literal("opened"), + number: z.number(), + pull_request: pullRequestSchema.and( + z.object({ + state: z.literal("open"), + closed_at: z.null(), + merged_at: z.null(), + active_lock_reason: z.null(), + merged_by: z.null(), + }) + ), + repository: repositorySchema, + installation: installationLiteSchema.optional(), + organization: organizationSchema.optional(), + sender: userSchema, +}); + +export const pullRequestQueuedEventSchema = z.object({ + action: z.literal("queued"), + number: z.number(), + pull_request: pullRequestSchema, + repository: repositorySchema, + installation: installationLiteSchema.optional(), + organization: organizationSchema.optional(), + sender: userSchema, +}); + +export const pullRequestReadyForReviewEventSchema = z.object({ + action: z.literal("ready_for_review"), + number: z.number(), + pull_request: pullRequestSchema.and( + z.object({ + state: z.literal("open"), + closed_at: z.null(), + merged_at: z.null(), + draft: z.literal(false), + merged: z.boolean(), + merged_by: z.null(), + }) + ), + repository: repositorySchema, + installation: installationLiteSchema.optional(), + organization: organizationSchema.optional(), + sender: userSchema, +}); + +export const pullRequestReopenedEventSchema = z.object({ + action: z.literal("reopened"), + number: z.number(), + pull_request: pullRequestSchema.and( + z.object({ + state: z.literal("open"), + closed_at: z.null(), + merged_at: z.null(), + merged: z.boolean(), + merged_by: z.null(), + }) + ), + repository: repositorySchema, + installation: installationLiteSchema.optional(), + organization: organizationSchema.optional(), + sender: userSchema, +}); + +export const pullRequestSynchronizeEventSchema = z.object({ + action: z.literal("synchronize"), + number: z.number(), + before: z.string(), + after: z.string(), + pull_request: pullRequestSchema, + repository: repositorySchema, + installation: installationLiteSchema.optional(), + organization: organizationSchema.optional(), + sender: userSchema, +}); + +export const pullRequestUnassignedEventSchema = z.object({ + action: z.literal("unassigned"), + number: z.number(), + pull_request: pullRequestSchema, + assignee: userSchema, + repository: repositorySchema, + installation: installationLiteSchema.optional(), + organization: organizationSchema.optional(), + sender: userSchema, +}); + +export const pullRequestUnlabeledEventSchema = z.object({ + action: z.literal("unlabeled"), + number: z.number(), + pull_request: pullRequestSchema, + label: labelSchema, + repository: repositorySchema, + installation: installationLiteSchema.optional(), + organization: organizationSchema.optional(), + sender: userSchema, +}); + +export const pullRequestUnlockedEventSchema = z.object({ + action: z.literal("unlocked"), + number: z.number(), + pull_request: pullRequestSchema, + repository: repositorySchema, + installation: installationLiteSchema.optional(), + organization: organizationSchema.optional(), + sender: userSchema, +}); + +export const pullRequestReviewRequestRemovedEventSchema = z.object({ + action: z.literal("review_request_removed"), + number: z.number(), + pull_request: pullRequestSchema, + requested_reviewer: userSchema.optional(), + requested_team: teamSchema.optional(), + repository: repositorySchema, + installation: installationLiteSchema.optional(), + organization: organizationSchema.optional(), + sender: userSchema, +}); + +export const pullRequestReviewRequestedEventSchema = z.object({ + action: z.literal("review_requested"), + number: z.number(), + pull_request: pullRequestSchema, + requested_reviewer: userSchema.optional(), + requested_team: teamSchema.optional(), + repository: repositorySchema, + installation: installationLiteSchema.optional(), + organization: organizationSchema.optional(), + sender: userSchema, +}); + +export const pullRequestEventSchema: z.ZodDiscriminatedUnion< + "action", + [ + typeof pullRequestAssignedEventSchema, + typeof pullRequestAutoMergeDisabledEventSchema, + typeof pullRequestAutoMergeEnabledEventSchema, + typeof pullRequestClosedEventSchema, + typeof pullRequestConvertedToDraftEventSchema, + typeof pullRequestDequeuedEventSchema, + typeof pullRequestEditedEventSchema, + typeof pullRequestLabeledEventSchema, + typeof pullRequestLockedEventSchema, + typeof pullRequestOpenedEventSchema, + typeof pullRequestQueuedEventSchema, + typeof pullRequestReadyForReviewEventSchema, + typeof pullRequestReopenedEventSchema, + typeof pullRequestReviewRequestRemovedEventSchema, + typeof pullRequestReviewRequestedEventSchema, + typeof pullRequestSynchronizeEventSchema, + typeof pullRequestUnassignedEventSchema, + typeof pullRequestUnlabeledEventSchema, + typeof pullRequestUnlockedEventSchema + ] +> = z.discriminatedUnion("action", [ + pullRequestAssignedEventSchema, + pullRequestAutoMergeDisabledEventSchema, + pullRequestAutoMergeEnabledEventSchema, + pullRequestClosedEventSchema, + pullRequestConvertedToDraftEventSchema, + pullRequestDequeuedEventSchema, + pullRequestEditedEventSchema, + pullRequestLabeledEventSchema, + pullRequestLockedEventSchema, + pullRequestOpenedEventSchema, + pullRequestQueuedEventSchema, + pullRequestReadyForReviewEventSchema, + pullRequestReopenedEventSchema, + pullRequestReviewRequestRemovedEventSchema, + pullRequestReviewRequestedEventSchema, + pullRequestSynchronizeEventSchema, + pullRequestUnassignedEventSchema, + pullRequestUnlabeledEventSchema, + pullRequestUnlockedEventSchema, +]); diff --git a/packages/trigger-providers/src/providers/github/schemas/shared.ts b/packages/trigger-providers/src/providers/github/schemas/shared.ts index 28278e662..8c14efaac 100644 --- a/packages/trigger-providers/src/providers/github/schemas/shared.ts +++ b/packages/trigger-providers/src/providers/github/schemas/shared.ts @@ -165,6 +165,48 @@ export const repositorySchema = z.object({ organization: z.string().optional(), }); +export const teamSchema = z.object({ + name: z.string(), + id: z.number(), + node_id: z.string(), + slug: z.string(), + description: z.string().nullable(), + privacy: z.union([ + z.literal("open"), + z.literal("closed"), + z.literal("secret"), + ]), + url: z.string(), + html_url: z.string(), + members_url: z.string(), + repositories_url: z.string(), + permission: z.string(), + parent: z + .object({ + name: z.string(), + id: z.number(), + node_id: z.string(), + slug: z.string(), + description: z.string().nullable(), + privacy: z.union([ + z.literal("open"), + z.literal("closed"), + z.literal("secret"), + ]), + url: z.string(), + html_url: z.string(), + members_url: z.string(), + repositories_url: z.string(), + permission: z.string(), + }) + .optional() + .nullable(), +}); + +export const linkSchema = z.object({ + href: z.string(), +}); + export const appSchema = z.object({ id: z.number(), slug: z.string().optional(),