diff --git a/apps/webapp/app/models/workflowListPresenter.server.ts b/apps/webapp/app/models/workflowListPresenter.server.ts index 956610654..e11d798eb 100644 --- a/apps/webapp/app/models/workflowListPresenter.server.ts +++ b/apps/webapp/app/models/workflowListPresenter.server.ts @@ -4,8 +4,8 @@ import { SlackInteractionSourceSchema, } from "@trigger.dev/common-schemas"; import cronstrue from "cronstrue"; -import type { DisplayProperties } from "internal-integrations"; -import { github } from "internal-integrations"; +import type { DisplayProperties } from "@trigger.dev/integration-sdk"; +import * as github from "@trigger.dev/github/internal"; import invariant from "tiny-invariant"; import { triggerLabel } from "~/components/triggers/triggerLabel"; import type { PrismaClient } from "~/db.server"; diff --git a/apps/webapp/app/services/externalSources/handleExternalSource.server.ts b/apps/webapp/app/services/externalSources/handleExternalSource.server.ts index 7a5de1583..7a697f935 100644 --- a/apps/webapp/app/services/externalSources/handleExternalSource.server.ts +++ b/apps/webapp/app/services/externalSources/handleExternalSource.server.ts @@ -1,8 +1,8 @@ import type { PrismaClient } from "~/db.server"; import { prisma } from "~/db.server"; -import { github } from "internal-integrations"; +import * as github from "@trigger.dev/github/internal"; import type { ExternalSourceWithConnection } from "~/models/externalSource.server"; -import type { NormalizedRequest } from "internal-integrations"; +import type { NormalizedRequest } from "@trigger.dev/integration-sdk"; import { IngestEvent } from "../events/ingest.server"; import { ManualWebhookSourceSchema } from "@trigger.dev/common-schemas"; import { createHmac, timingSafeEqual } from "node:crypto"; diff --git a/apps/webapp/app/services/externalSources/registerExternalSource.server.ts b/apps/webapp/app/services/externalSources/registerExternalSource.server.ts index 282d940df..114bc71e0 100644 --- a/apps/webapp/app/services/externalSources/registerExternalSource.server.ts +++ b/apps/webapp/app/services/externalSources/registerExternalSource.server.ts @@ -1,10 +1,9 @@ import type { APIConnection, ExternalSource } from ".prisma/client"; -import type { AccessInfo } from "internal-integrations"; -import { github } from "internal-integrations"; +import type { AccessInfo } from "@trigger.dev/integration-sdk"; +import * as github from "@trigger.dev/github/internal"; import crypto from "node:crypto"; import type { PrismaClient } from "~/db.server"; import { prisma } from "~/db.server"; -import { env } from "~/env.server"; import { buildExternalSourceUrl, findExternalSourceById, diff --git a/apps/webapp/app/services/fetches/performFetchRequest.server.ts b/apps/webapp/app/services/fetches/performFetchRequest.server.ts index bd780eef4..492ff00e3 100644 --- a/apps/webapp/app/services/fetches/performFetchRequest.server.ts +++ b/apps/webapp/app/services/fetches/performFetchRequest.server.ts @@ -4,7 +4,7 @@ import { FetchRequestSchema } from "@trigger.dev/common-schemas"; import type { NormalizedResponse, PerformedRequestResponse, -} from "internal-integrations"; +} from "@trigger.dev/integration-sdk"; import type { z } from "zod"; import type { PrismaClient } from "~/db.server"; import { prisma } from "~/db.server"; diff --git a/apps/webapp/app/services/requests/performIntegrationRequest.server.ts b/apps/webapp/app/services/requests/performIntegrationRequest.server.ts index 0c2275dfc..b59b99f75 100644 --- a/apps/webapp/app/services/requests/performIntegrationRequest.server.ts +++ b/apps/webapp/app/services/requests/performIntegrationRequest.server.ts @@ -3,7 +3,7 @@ import type { CacheService, NormalizedResponse, PerformedRequestResponse, -} from "internal-integrations"; +} from "@trigger.dev/integration-sdk"; import { resend, shopify, slack } from "internal-integrations"; import type { PrismaClient } from "~/db.server"; import { prisma } from "~/db.server"; diff --git a/apps/webapp/app/services/workflows/registerWorkflow.server.ts b/apps/webapp/app/services/workflows/registerWorkflow.server.ts index 90da212f7..0a0a8f6d8 100644 --- a/apps/webapp/app/services/workflows/registerWorkflow.server.ts +++ b/apps/webapp/app/services/workflows/registerWorkflow.server.ts @@ -1,4 +1,4 @@ -import { github } from "internal-integrations"; +import * as github from "@trigger.dev/github/internal"; import type { WorkflowMetadata } from "internal-platform"; import { WorkflowMetadataSchema } from "internal-platform"; import crypto from "node:crypto"; diff --git a/apps/webapp/package.json b/apps/webapp/package.json index 8f3b9b102..8678e1abe 100644 --- a/apps/webapp/package.json +++ b/apps/webapp/package.json @@ -64,6 +64,7 @@ "@tanstack/react-table": "^8.0.0-alpha.87", "@trigger.dev/common-schemas": "workspace:*", "@trigger.dev/providers": "workspace:*", + "@trigger.dev/github": "workspace:*", "@typeform/embed-react": "^2.14.1", "@uiw/react-codemirror": "^4.13.2", "bcryptjs": "^2.4.3", @@ -134,6 +135,7 @@ "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^14.4.3", "@trigger.dev/tailwind-config": "workspace:*", + "@trigger.dev/integration-sdk": "workspace:*", "@types/bcryptjs": "^2.4.2", "@types/compression": "^1.7.2", "@types/eslint": "^8.4.6", @@ -186,4 +188,4 @@ "engines": { "node": ">=16.0.0" } -} +} \ No newline at end of file diff --git a/apps/webapp/remix.config.js b/apps/webapp/remix.config.js index 9861e5811..1bf20b954 100644 --- a/apps/webapp/remix.config.js +++ b/apps/webapp/remix.config.js @@ -14,9 +14,11 @@ module.exports = { "internal-integrations", "internal-bridge", "@trigger.dev/providers", + "@trigger.dev/github", "@trigger.dev/common-schemas", "@trigger.dev/sdk", "@trigger.dev/integrations", + "@trigger.dev/integration-sdk", "emails", "internal-pulsar", ], @@ -32,6 +34,7 @@ module.exports = { "../../packages/internal-platform/src/**/*", "../../packages/trigger-sdk/src/**/*", "../../packages/trigger-integrations/src/**/*", + "../../integrations/github/src/**/*", ]; }, }; diff --git a/apps/webapp/tsconfig.json b/apps/webapp/tsconfig.json index eca320874..b9d37884b 100644 --- a/apps/webapp/tsconfig.json +++ b/apps/webapp/tsconfig.json @@ -43,7 +43,15 @@ "emails": ["../../packages/emails/src/index"], "emails/*": ["../../packages/emails/src/*"], "internal-pulsar": ["../../packages/internal-pulsar/src/index"], - "internal-pulsar/*": ["../../packages/internal-pulsar/src/*"] + "internal-pulsar/*": ["../../packages/internal-pulsar/src/*"], + "@trigger.dev/integration-sdk": [ + "../../packages/integration-sdk/src/index" + ], + "@trigger.dev/integration-sdk/*": [ + "../../packages/integration-sdk/src/*" + ], + "@trigger.dev/github": ["../../integrations/github/src/index"], + "@trigger.dev/github/*": ["../../integrations/github/src/*"] }, "noEmit": true } diff --git a/config-packages/tsconfig/examples.json b/config-packages/tsconfig/examples.json index d150976ca..48479fab9 100644 --- a/config-packages/tsconfig/examples.json +++ b/config-packages/tsconfig/examples.json @@ -11,18 +11,31 @@ "forceConsistentCasingInFileNames": true, "experimentalDecorators": true, "emitDecoratorMetadata": true, - "lib": ["esnext", "dom"], + "lib": [ + "esnext", + "dom" + ], "outDir": "lib", "moduleResolution": "node", "paths": { - "@trigger.dev/sdk": ["../../packages/trigger-sdk/src/index"], - "@trigger.dev/sdk/*": ["../../packages/trigger-sdk/src/*"], + "@trigger.dev/sdk": [ + "../../packages/trigger-sdk/src/index" + ], + "@trigger.dev/sdk/*": [ + "../../packages/trigger-sdk/src/*" + ], "@trigger.dev/integrations": [ "../../packages/trigger-integrations/src/index" ], "@trigger.dev/integrations/*": [ "../../packages/trigger-integrations/src/*" + ], + "@trigger.dev/github": [ + "../../integrations/github/src/index" + ], + "@trigger.dev/github/*": [ + "../../integrations/github/src/*" ] } } -} +} \ No newline at end of file diff --git a/examples/github-webhook/package.json b/examples/github-webhook/package.json index 3f8e14f36..1f97af126 100644 --- a/examples/github-webhook/package.json +++ b/examples/github-webhook/package.json @@ -4,7 +4,7 @@ "version": "0.0.1", "description": "Example trigger.dev workflow that uses the Github Webhook integration", "dependencies": { - "@trigger.dev/integrations": "workspace:*", + "@trigger.dev/github": "workspace:*", "@trigger.dev/sdk": "workspace:*", "zod": "^3.20.2" }, @@ -16,4 +16,4 @@ "scripts": { "dev": "tsx src/index.ts" } -} +} \ No newline at end of file diff --git a/examples/github-webhook/src/index.ts b/examples/github-webhook/src/index.ts index 2d6e3b899..ae6cae919 100644 --- a/examples/github-webhook/src/index.ts +++ b/examples/github-webhook/src/index.ts @@ -1,5 +1,5 @@ import { Trigger } from "@trigger.dev/sdk"; -import { github } from "@trigger.dev/integrations"; +import { events } from "@trigger.dev/github"; new Trigger({ id: "github-webhook-9", @@ -7,7 +7,7 @@ new Trigger({ apiKey: "trigger_dev_zC25mKNn6c0q", endpoint: "ws://localhost:8889/ws", logLevel: "debug", - on: github.events.issueEvent({ + on: events.issueEvent({ repo: "triggerdotdev/trigger.dev-examples", }), run: async (event, ctx) => { @@ -23,7 +23,7 @@ new Trigger({ apiKey: "trigger_dev_zC25mKNn6c0q", endpoint: "ws://localhost:8889/ws", logLevel: "debug", - on: github.events.issueCommentEvent({ + on: events.issueCommentEvent({ repo: "triggerdotdev/trigger.dev-examples", }), run: async (event, ctx) => { @@ -39,7 +39,7 @@ new Trigger({ apiKey: "trigger_dev_zC25mKNn6c0q", endpoint: "ws://localhost:8889/ws", logLevel: "debug", - on: github.events.pullRequestEvent({ + on: events.pullRequestEvent({ repo: "triggerdotdev/trigger.dev-examples", }), run: async (event, ctx) => { @@ -55,7 +55,7 @@ new Trigger({ apiKey: "trigger_dev_zC25mKNn6c0q", endpoint: "ws://localhost:8889/ws", logLevel: "debug", - on: github.events.pullRequestCommentEvent({ + on: events.pullRequestCommentEvent({ repo: "triggerdotdev/trigger.dev-examples", }), run: async (event, ctx) => { @@ -71,7 +71,7 @@ new Trigger({ apiKey: "trigger_dev_zC25mKNn6c0q", endpoint: "ws://localhost:8889/ws", logLevel: "debug", - on: github.events.pullRequestReviewEvent({ + on: events.pullRequestReviewEvent({ repo: "triggerdotdev/trigger.dev-examples", }), run: async (event, ctx) => { @@ -87,7 +87,7 @@ new Trigger({ apiKey: "trigger_dev_zC25mKNn6c0q", endpoint: "ws://localhost:8889/ws", logLevel: "debug", - on: github.events.pushEvent({ + on: events.pushEvent({ repo: "triggerdotdev/trigger.dev-examples", }), run: async (event, ctx) => { @@ -103,7 +103,7 @@ new Trigger({ apiKey: "trigger_dev_zC25mKNn6c0q", endpoint: "ws://localhost:8889/ws", logLevel: "debug", - on: github.events.commitCommentEvent({ + on: events.commitCommentEvent({ repo: "triggerdotdev/trigger.dev-examples", }), run: async (event, ctx) => { @@ -119,7 +119,7 @@ new Trigger({ apiKey: "trigger_dev_zC25mKNn6c0q", endpoint: "ws://localhost:8889/ws", logLevel: "debug", - on: github.events.newStarEvent({ + on: events.newStarEvent({ repo: "triggerdotdev/trigger.dev-examples", }), run: async (event, ctx) => { diff --git a/integrations/github/README.md b/integrations/github/README.md new file mode 100644 index 000000000..1ad0e1497 --- /dev/null +++ b/integrations/github/README.md @@ -0,0 +1,3 @@ +# Trigger.dev GitHub integration + +View more documentation [here](https://docs.trigger.dev) diff --git a/integrations/github/package.json b/integrations/github/package.json new file mode 100644 index 000000000..d2c15196e --- /dev/null +++ b/integrations/github/package.json @@ -0,0 +1,39 @@ +{ + "name": "@trigger.dev/github", + "version": "0.1.16", + "description": "The official GitHub integration for Trigger.dev", + "exports": { + ".": { + "import": "./dist/index.js", + "require": "./dist/index.js", + "types": "./dist/index.d.ts" + }, + "./package.json": "./package.json" + }, + "publishConfig": { + "access": "public" + }, + "files": [ + "dist" + ], + "devDependencies": { + "@trigger.dev/sdk": "workspace:*", + "@trigger.dev/tsconfig": "workspace:*", + "@trigger.dev/integration-sdk": "workspace:*", + "@types/node": "16", + "rimraf": "^3.0.2", + "tsup": "^6.5.0" + }, + "peerDependencies": { + "@trigger.dev/sdk": "workspace:*" + }, + "scripts": { + "clean": "rimraf dist", + "build": "npm run clean && npm run build:tsup", + "build:tsup": "tsup" + }, + "dependencies": { + "@octokit/webhooks": "^10.4.0", + "zod": "^3.20.2" + } +} \ No newline at end of file diff --git a/integrations/github/src/events.ts b/integrations/github/src/events.ts new file mode 100644 index 000000000..392b21823 --- /dev/null +++ b/integrations/github/src/events.ts @@ -0,0 +1,239 @@ +import type { TriggerEvent } from "@trigger.dev/sdk"; +import * as schemas from "./schemas"; + +export function commitCommentEvent(params: { + repo: string; +}): TriggerEvent { + return { + metadata: { + type: "WEBHOOK", + service: "github", + name: "commit_comment", + filter: { + service: ["github"], + payload: { + repository: { + full_name: [params.repo], + }, + }, + event: ["commit_comment"], + }, + source: schemas.WebhookSourceSchema.parse({ + subresource: "repository", + scopes: ["repo"], + repo: params.repo, + events: ["commit_comment"], + }), + manualRegistration: false, + }, + schema: schemas.commitComments.commitCommentEventSchema, + }; +} + +export function issueEvent(params: { + repo: string; +}): TriggerEvent { + return { + metadata: { + type: "WEBHOOK", + service: "github", + name: "issues", + filter: { + service: ["github"], + payload: { + repository: { + full_name: [params.repo], + }, + }, + event: ["issues"], + }, + source: schemas.WebhookSourceSchema.parse({ + subresource: "repository", + scopes: ["repo"], + repo: params.repo, + events: ["issues"], + }), + manualRegistration: false, + }, + schema: schemas.issues.issuesEventSchema, + }; +} + +export function issueCommentEvent(params: { + repo: string; +}): TriggerEvent { + return { + metadata: { + type: "WEBHOOK", + service: "github", + name: "issue_comment", + filter: { + service: ["github"], + payload: { + repository: { + full_name: [params.repo], + }, + }, + event: ["issue_comment"], + }, + source: schemas.WebhookSourceSchema.parse({ + subresource: "repository", + scopes: ["repo"], + repo: params.repo, + events: ["issue_comment"], + }), + manualRegistration: false, + }, + schema: 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: schemas.WebhookSourceSchema.parse({ + subresource: "repository", + scopes: ["repo"], + repo: params.repo, + events: ["pull_request"], + }), + manualRegistration: false, + }, + schema: schemas.pullRequest.pullRequestEventSchema, + }; +} + +export function pullRequestCommentEvent(params: { + repo: string; +}): TriggerEvent< + typeof schemas.pullRequestComments.pullRequestReviewCommentEventSchema +> { + return { + metadata: { + type: "WEBHOOK", + service: "github", + name: "pull_request_review_comment", + filter: { + service: ["github"], + payload: { + repository: { + full_name: [params.repo], + }, + }, + event: ["pull_request_review_comment"], + }, + source: schemas.WebhookSourceSchema.parse({ + subresource: "repository", + scopes: ["repo"], + repo: params.repo, + events: ["pull_request_review_comment"], + }), + manualRegistration: false, + }, + schema: schemas.pullRequestComments.pullRequestReviewCommentEventSchema, + }; +} + +export function pullRequestReviewEvent(params: { + repo: string; +}): TriggerEvent< + typeof schemas.pullRequestReviews.pullRequestReviewEventSchema +> { + return { + metadata: { + type: "WEBHOOK", + service: "github", + name: "pull_request_review", + filter: { + service: ["github"], + payload: { + repository: { + full_name: [params.repo], + }, + }, + event: ["pull_request_review"], + }, + source: schemas.WebhookSourceSchema.parse({ + subresource: "repository", + scopes: ["repo"], + repo: params.repo, + events: ["pull_request_review"], + }), + manualRegistration: false, + }, + schema: schemas.pullRequestReviews.pullRequestReviewEventSchema, + }; +} + +export function pushEvent(params: { + repo: string; +}): TriggerEvent { + return { + metadata: { + type: "WEBHOOK", + service: "github", + name: "push", + filter: { + service: ["github"], + payload: { + repository: { + full_name: [params.repo], + }, + }, + event: ["push"], + }, + source: schemas.WebhookSourceSchema.parse({ + subresource: "repository", + scopes: ["repo"], + repo: params.repo, + events: ["push"], + }), + manualRegistration: false, + }, + schema: schemas.push.pushEventSchema, + }; +} + +export function newStarEvent(params: { + repo: string; +}): TriggerEvent { + return { + metadata: { + type: "WEBHOOK", + service: "github", + name: "star", + filter: { + service: ["github"], + payload: { + repository: { + full_name: [params.repo], + }, + action: ["created"], + }, + event: ["star"], + }, + source: schemas.WebhookSourceSchema.parse({ + subresource: "repository", + scopes: ["repo"], + repo: params.repo, + events: ["star"], + }), + manualRegistration: false, + }, + schema: schemas.stars.starCreatedEventSchema, + }; +} diff --git a/integrations/github/src/index.ts b/integrations/github/src/index.ts new file mode 100644 index 000000000..324692d48 --- /dev/null +++ b/integrations/github/src/index.ts @@ -0,0 +1,3 @@ +import * as events from "./events"; + +export { events }; diff --git a/integrations/github/src/internal.ts b/integrations/github/src/internal.ts new file mode 100644 index 000000000..02e61a233 --- /dev/null +++ b/integrations/github/src/internal.ts @@ -0,0 +1,16 @@ +import { GitHubWebhookIntegration } from "./webhooks"; +import * as schemas from "./schemas"; + +export const provider = { + name: "GitHub", + slug: "github", + icon: "/integrations/github.png", + enabledFor: "all", + authentication: { + type: "oauth", + scopes: ["repo"], + }, + schemas, +}; + +export const webhooks = new GitHubWebhookIntegration(); diff --git a/integrations/github/src/schemas/commitComments.ts b/integrations/github/src/schemas/commitComments.ts new file mode 100644 index 000000000..6559778b4 --- /dev/null +++ b/integrations/github/src/schemas/commitComments.ts @@ -0,0 +1,31 @@ +import { z } from "zod"; +import { + userSchema, + authorAssociationSchema, + repositorySchema, + installationLiteSchema, + organizationSchema, +} from "./shared"; + +export const commitCommentEventSchema = z.object({ + action: z.literal("created"), + comment: z.object({ + url: z.string(), + html_url: z.string(), + id: z.number(), + node_id: z.string(), + user: userSchema, + position: z.number().nullable(), + line: z.number().nullable(), + path: z.string().nullable(), + commit_id: z.string(), + created_at: z.string(), + updated_at: z.string(), + author_association: authorAssociationSchema, + body: z.string(), + }), + repository: repositorySchema, + sender: userSchema, + installation: installationLiteSchema.optional(), + organization: organizationSchema.optional(), +}); diff --git a/integrations/github/src/schemas/index.ts b/integrations/github/src/schemas/index.ts new file mode 100644 index 000000000..081e78e72 --- /dev/null +++ b/integrations/github/src/schemas/index.ts @@ -0,0 +1,39 @@ +import { z } from "zod"; +import * as commitComments from "./commitComments"; +import * as issuesComments from "./issueComments"; +import * as issues from "./issues"; +import * as pullRequestComments from "./pullRequestComments"; +import * as pullRequestReviews from "./pullRequestReviews"; +import * as pullRequest from "./pullRequests"; +import * as push from "./pushes"; +import * as stars from "./stars"; + +export const WebhookRepoSourceSchema = z.object({ + subresource: z.literal("repository"), + scopes: z.array(z.string()), + repo: z.string(), + events: z.array(z.string()), +}); + +export const WebhookOrganizationSourceSchema = z.object({ + subresource: z.literal("organization"), + scopes: z.array(z.string()), + org: z.string(), + events: z.array(z.string()), +}); + +export const WebhookSourceSchema = z.union([ + WebhookRepoSourceSchema, + WebhookOrganizationSourceSchema, +]); + +export { + commitComments, + issues, + issuesComments, + pullRequest, + pullRequestComments, + pullRequestReviews, + push, + stars, +}; diff --git a/integrations/github/src/schemas/issueComments.ts b/integrations/github/src/schemas/issueComments.ts new file mode 100644 index 000000000..8be801aad --- /dev/null +++ b/integrations/github/src/schemas/issueComments.ts @@ -0,0 +1,91 @@ +import { z } from "zod"; +import { issueSchema } from "./issues"; +import { + appSchema, + authorAssociationSchema, + installationLiteSchema, + labelSchema, + organizationSchema, + reactionsSchema, + repositorySchema, + userSchema, +} from "./shared"; + +export const issueCommentSchema = z.object({ + url: z.string(), + html_url: z.string(), + issue_url: z.string(), + id: z.number(), + node_id: z.string(), + user: userSchema, + created_at: z.string(), + updated_at: z.string(), + author_association: authorAssociationSchema, + body: z.string(), + reactions: reactionsSchema, + performed_via_github_app: appSchema.nullable(), +}); + +export const issueCommentCreatedEventSchema = z.object({ + action: z.literal("created"), + issue: issueSchema.and( + z.object({ + assignee: userSchema.nullable(), + state: z.union([z.literal("open"), z.literal("closed")]), + locked: z.boolean(), + labels: z.array(labelSchema), + }) + ), + comment: issueCommentSchema, + repository: repositorySchema, + sender: userSchema, + installation: installationLiteSchema.optional(), + organization: organizationSchema.optional(), +}); + +export const issueCommentDeletedEventSchema = z.object({ + action: z.literal("deleted"), + issue: issueSchema.and( + z.object({ + assignee: userSchema.nullable(), + state: z.union([z.literal("open"), z.literal("closed")]), + locked: z.boolean(), + labels: z.array(labelSchema), + }) + ), + comment: issueCommentSchema, + repository: repositorySchema, + sender: userSchema, + installation: installationLiteSchema.optional(), + organization: organizationSchema.optional(), +}); + +export const issueCommentEditedEventSchema = z.object({ + action: z.literal("edited"), + changes: z.object({ + body: z + .object({ + from: z.string(), + }) + .optional(), + }), + issue: issueSchema.and( + z.object({ + assignee: userSchema.nullable(), + state: z.union([z.literal("open"), z.literal("closed")]), + locked: z.boolean(), + labels: z.array(labelSchema), + }) + ), + comment: issueCommentSchema, + repository: repositorySchema, + sender: userSchema, + installation: installationLiteSchema.optional(), + organization: organizationSchema.optional(), +}); + +export const issueCommentEventSchema = z.discriminatedUnion("action", [ + issueCommentCreatedEventSchema, + issueCommentDeletedEventSchema, + issueCommentEditedEventSchema, +]); diff --git a/integrations/github/src/schemas/issues.ts b/integrations/github/src/schemas/issues.ts new file mode 100644 index 000000000..224db1a82 --- /dev/null +++ b/integrations/github/src/schemas/issues.ts @@ -0,0 +1,311 @@ +import { z } from "zod"; +import { + appSchema, + authorAssociationSchema, + installationLiteSchema, + labelSchema, + milestoneSchema, + organizationSchema, + reactionsSchema, + repositorySchema, + userSchema, +} from "./shared"; + +export const issueSchema = z.object({ + url: z.string(), + repository_url: z.string(), + labels_url: z.string(), + comments_url: z.string(), + events_url: z.string(), + html_url: z.string(), + id: z.number(), + node_id: z.string(), + number: z.number(), + title: z.string(), + user: userSchema, + labels: z.array(labelSchema).optional(), + state: z.union([z.literal("open"), z.literal("closed")]).optional(), + locked: z.boolean().optional(), + assignee: userSchema.optional().nullable(), + assignees: z.array(userSchema), + milestone: milestoneSchema.nullable(), + comments: z.number(), + created_at: z.string(), + updated_at: z.string(), + closed_at: z.string().nullable(), + author_association: authorAssociationSchema, + active_lock_reason: z + .union([ + z.literal("resolved"), + z.literal("off-topic"), + z.literal("too heated"), + z.literal("spam"), + ]) + .nullable(), + draft: z.boolean().optional(), + performed_via_github_app: appSchema.optional().nullable(), + pull_request: z + .object({ + url: z.string().optional(), + html_url: z.string().optional(), + diff_url: z.string().optional(), + patch_url: z.string().optional(), + merged_at: z.string().optional().nullable(), + }) + .optional(), + body: z.string().nullable(), + reactions: reactionsSchema, + timeline_url: z.string().optional(), + state_reason: z.string().optional().nullable(), +}); + +export const issuesAssignedEventSchema = z.object({ + action: z.literal("assigned"), + issue: issueSchema, + assignee: userSchema.optional().nullable(), + repository: repositorySchema, + sender: userSchema, + installation: installationLiteSchema.optional(), + organization: organizationSchema.optional(), +}); + +export const issuesClosedEventSchema = z.object({ + action: z.literal("closed"), + issue: issueSchema.and( + z.object({ + state: z.literal("closed"), + closed_at: z.string(), + }) + ), + repository: repositorySchema, + sender: userSchema, + installation: installationLiteSchema.optional(), + organization: organizationSchema.optional(), +}); + +export const issuesDeletedEventSchema = z.object({ + action: z.literal("deleted"), + issue: issueSchema, + repository: repositorySchema, + sender: userSchema, + installation: installationLiteSchema.optional(), + organization: organizationSchema.optional(), +}); + +export const issuesDemilestonedEventSchema = z.object({ + action: z.literal("demilestoned"), + issue: issueSchema.and( + z.object({ + milestone: z.null(), + }) + ), + milestone: milestoneSchema, + repository: repositorySchema, + sender: userSchema, + installation: installationLiteSchema.optional(), + organization: organizationSchema.optional(), +}); + +export const issuesEditedEventSchema = z.object({ + action: z.literal("edited"), + issue: issueSchema, + label: labelSchema.optional(), + changes: z.object({ + body: z + .object({ + from: z.string(), + }) + .optional(), + title: z + .object({ + from: z.string(), + }) + .optional(), + }), + repository: repositorySchema, + sender: userSchema, + installation: installationLiteSchema.optional(), + organization: organizationSchema.optional(), +}); + +export const issuesLabeledEventSchema = z.object({ + action: z.literal("labeled"), + issue: issueSchema, + label: labelSchema.optional(), + repository: repositorySchema, + sender: userSchema, + installation: installationLiteSchema.optional(), + organization: organizationSchema.optional(), +}); + +export const issuesLockedEventSchema = z.object({ + action: z.literal("locked"), + issue: issueSchema.and( + z.object({ + locked: z.literal(true), + active_lock_reason: z + .union([ + z.literal("resolved"), + z.literal("off-topic"), + z.literal("too heated"), + z.literal("spam"), + ]) + .nullable(), + }) + ), + repository: repositorySchema, + sender: userSchema, + installation: installationLiteSchema.optional(), + organization: organizationSchema.optional(), +}); + +export const issuesMilestonedEventSchema = z.object({ + action: z.literal("milestoned"), + issue: issueSchema.and( + z.object({ + milestone: milestoneSchema, + }) + ), + milestone: milestoneSchema, + repository: repositorySchema, + sender: userSchema, + installation: installationLiteSchema.optional(), + organization: organizationSchema.optional(), +}); + +export const issuesOpenedEventSchema = z.object({ + action: z.literal("opened"), + changes: z + .object({ + old_issue: issueSchema, + old_repository: repositorySchema, + }) + .optional(), + issue: issueSchema.and( + z.object({ + state: z.literal("open"), + closed_at: z.null(), + }) + ), + repository: repositorySchema, + sender: userSchema, + installation: installationLiteSchema.optional(), + organization: organizationSchema.optional(), +}); + +export const issuesPinnedEventSchema = z.object({ + action: z.literal("pinned"), + issue: issueSchema, + repository: repositorySchema, + sender: userSchema, + installation: installationLiteSchema.optional(), + organization: organizationSchema.optional(), +}); + +export const issuesReopenedEventSchema = z.object({ + action: z.literal("reopened"), + issue: issueSchema.and( + z.object({ + state: z.literal("open"), + }) + ), + repository: repositorySchema, + sender: userSchema, + installation: installationLiteSchema.optional(), + organization: organizationSchema.optional(), +}); + +export const issuesTransferredEventSchema = z.object({ + action: z.literal("transferred"), + changes: z.object({ + new_issue: issueSchema, + new_repository: repositorySchema, + }), + issue: issueSchema, + repository: repositorySchema, + sender: userSchema, + installation: installationLiteSchema.optional(), + organization: organizationSchema.optional(), +}); + +export const issuesUnassignedEventSchema = z.object({ + action: z.literal("unassigned"), + issue: issueSchema, + assignee: userSchema.optional().nullable(), + repository: repositorySchema, + sender: userSchema, + installation: installationLiteSchema.optional(), + organization: organizationSchema.optional(), +}); + +export const issuesUnlabeledEventSchema = z.object({ + action: z.literal("unlabeled"), + issue: issueSchema, + label: labelSchema.optional(), + repository: repositorySchema, + sender: userSchema, + installation: installationLiteSchema.optional(), + organization: organizationSchema.optional(), +}); + +export const issuesUnlockedEventSchema = z.object({ + action: z.literal("unlocked"), + issue: issueSchema.and( + z.object({ + locked: z.literal(false), + active_lock_reason: z.null(), + }) + ), + repository: repositorySchema, + sender: userSchema, + installation: installationLiteSchema.optional(), + organization: organizationSchema.optional(), +}); + +export const issuesUnpinnedEventSchema = z.object({ + action: z.literal("unpinned"), + issue: issueSchema, + repository: repositorySchema, + sender: userSchema, + installation: installationLiteSchema.optional(), + organization: organizationSchema.optional(), +}); + +export const issuesEventSchema: z.ZodDiscriminatedUnion< + "action", + [ + typeof issuesAssignedEventSchema, + typeof issuesClosedEventSchema, + typeof issuesDeletedEventSchema, + typeof issuesDemilestonedEventSchema, + typeof issuesEditedEventSchema, + typeof issuesLabeledEventSchema, + typeof issuesLockedEventSchema, + typeof issuesMilestonedEventSchema, + typeof issuesOpenedEventSchema, + typeof issuesPinnedEventSchema, + typeof issuesReopenedEventSchema, + typeof issuesTransferredEventSchema, + typeof issuesUnassignedEventSchema, + typeof issuesUnlabeledEventSchema, + typeof issuesUnlockedEventSchema, + typeof issuesUnpinnedEventSchema + ] +> = z.discriminatedUnion("action", [ + issuesAssignedEventSchema, + issuesClosedEventSchema, + issuesDeletedEventSchema, + issuesDemilestonedEventSchema, + issuesEditedEventSchema, + issuesLabeledEventSchema, + issuesLockedEventSchema, + issuesMilestonedEventSchema, + issuesOpenedEventSchema, + issuesPinnedEventSchema, + issuesReopenedEventSchema, + issuesTransferredEventSchema, + issuesUnassignedEventSchema, + issuesUnlabeledEventSchema, + issuesUnlockedEventSchema, + issuesUnpinnedEventSchema, +]); diff --git a/integrations/github/src/schemas/pullRequestComments.ts b/integrations/github/src/schemas/pullRequestComments.ts new file mode 100644 index 000000000..f386dd2f2 --- /dev/null +++ b/integrations/github/src/schemas/pullRequestComments.ts @@ -0,0 +1,285 @@ +import { z } from "zod"; +import { + userSchema, + teamSchema, + labelSchema, + milestoneSchema, + repositorySchema, + linkSchema, + authorAssociationSchema, + installationLiteSchema, + organizationSchema, + reactionsSchema, + pullRequestAutoMergeSchema, +} from "./shared"; + +export const pullRequestReviewCommentSchema = z.object({ + url: z.string(), + pull_request_review_id: z.number(), + id: z.number(), + node_id: z.string(), + diff_hunk: z.string(), + path: z.string(), + position: z.number().nullable(), + original_position: z.number(), + commit_id: z.string(), + original_commit_id: z.string(), + user: userSchema, + body: z.string(), + created_at: z.string(), + updated_at: z.string(), + html_url: z.string(), + pull_request_url: z.string(), + author_association: authorAssociationSchema, + _links: z.object({ + self: linkSchema, + html: linkSchema, + pull_request: linkSchema, + }), + reactions: reactionsSchema, + start_line: z.number().nullable(), + original_start_line: z.number().nullable(), + start_side: z.union([z.literal("LEFT"), z.literal("RIGHT")]).nullable(), + line: z.number().nullable(), + original_line: z.number(), + side: z.union([z.literal("LEFT"), z.literal("RIGHT")]), + in_reply_to_id: z.number().optional(), +}); + +export const pullRequestReviewCommentCreatedEventSchema = z.object({ + action: z.literal("created"), + comment: pullRequestReviewCommentSchema, + pull_request: 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(), + draft: z.boolean().optional(), + 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, + }), + auto_merge: pullRequestAutoMergeSchema.optional().nullable(), + author_association: authorAssociationSchema, + active_lock_reason: z + .union([ + z.literal("resolved"), + z.literal("off-topic"), + z.literal("too heated"), + z.literal("spam"), + ]) + .nullable(), + }), + repository: repositorySchema, + installation: installationLiteSchema.optional(), + organization: organizationSchema.optional(), + sender: userSchema, +}); + +export const pullRequestReviewCommentDeletedEventSchema = z.object({ + action: z.literal("deleted"), + comment: pullRequestReviewCommentSchema, + pull_request: 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(), + draft: z.boolean().optional(), + 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, + }), + auto_merge: pullRequestAutoMergeSchema.optional().nullable(), + author_association: authorAssociationSchema, + active_lock_reason: z + .union([ + z.literal("resolved"), + z.literal("off-topic"), + z.literal("too heated"), + z.literal("spam"), + ]) + .nullable(), + }), + repository: repositorySchema, + installation: installationLiteSchema.optional(), + organization: organizationSchema.optional(), + sender: userSchema, +}); + +export const pullRequestReviewCommentEditedEventSchema = z.object({ + action: z.literal("edited"), + changes: z.object({ + body: z + .object({ + from: z.string(), + }) + .optional(), + }), + comment: pullRequestReviewCommentSchema, + pull_request: 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(), + draft: z.boolean().optional(), + 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, + }), + auto_merge: pullRequestAutoMergeSchema.optional().nullable(), + author_association: authorAssociationSchema, + active_lock_reason: z + .union([ + z.literal("resolved"), + z.literal("off-topic"), + z.literal("too heated"), + z.literal("spam"), + ]) + .nullable(), + }), + repository: repositorySchema, + installation: installationLiteSchema.optional(), + organization: organizationSchema.optional(), + sender: userSchema, +}); + +export const pullRequestReviewCommentEventSchema = z.union([ + pullRequestReviewCommentCreatedEventSchema, + pullRequestReviewCommentDeletedEventSchema, + pullRequestReviewCommentEditedEventSchema, +]); diff --git a/integrations/github/src/schemas/pullRequestReviews.ts b/integrations/github/src/schemas/pullRequestReviews.ts new file mode 100644 index 000000000..378ade5b4 --- /dev/null +++ b/integrations/github/src/schemas/pullRequestReviews.ts @@ -0,0 +1,79 @@ +import { z } from "zod"; +import { + repositorySchema, + installationLiteSchema, + organizationSchema, + userSchema, + authorAssociationSchema, + linkSchema, + simplePullRequestSchema, +} from "./shared"; + +export const pullRequestReviewSchema = z.object({ + id: z.number(), + node_id: z.string(), + user: userSchema, + body: z.string().nullable(), + commit_id: z.string(), + submitted_at: z.string().nullable(), + state: z.union([ + z.literal("commented"), + z.literal("changes_requested"), + z.literal("approved"), + z.literal("dismissed"), + ]), + html_url: z.string(), + pull_request_url: z.string(), + author_association: authorAssociationSchema, + _links: z.object({ + html: linkSchema, + pull_request: linkSchema, + }), +}); + +export const pullRequestReviewDismissedEventSchema = z.object({ + action: z.literal("dismissed"), + review: pullRequestReviewSchema.and( + z.object({ + state: z.literal("dismissed"), + }) + ), + pull_request: simplePullRequestSchema, + repository: repositorySchema, + installation: installationLiteSchema.optional(), + organization: organizationSchema.optional(), + sender: userSchema, +}); + +export const pullRequestReviewEditedEventSchema = z.object({ + action: z.literal("edited"), + changes: z.object({ + body: z + .object({ + from: z.string(), + }) + .optional(), + }), + review: pullRequestReviewSchema, + pull_request: simplePullRequestSchema, + repository: repositorySchema, + installation: installationLiteSchema.optional(), + organization: organizationSchema.optional(), + sender: userSchema, +}); + +export const pullRequestReviewSubmittedEventSchema = z.object({ + action: z.literal("submitted"), + review: pullRequestReviewSchema, + pull_request: simplePullRequestSchema, + repository: repositorySchema, + installation: installationLiteSchema.optional(), + organization: organizationSchema.optional(), + sender: userSchema, +}); + +export const pullRequestReviewEventSchema = z.union([ + pullRequestReviewDismissedEventSchema, + pullRequestReviewEditedEventSchema, + pullRequestReviewSubmittedEventSchema, +]); diff --git a/integrations/github/src/schemas/pullRequests.ts b/integrations/github/src/schemas/pullRequests.ts new file mode 100644 index 000000000..448bd754b --- /dev/null +++ b/integrations/github/src/schemas/pullRequests.ts @@ -0,0 +1,401 @@ +import { z } from "zod"; +import { + authorAssociationSchema, + installationLiteSchema, + labelSchema, + linkSchema, + milestoneSchema, + organizationSchema, + pullRequestAutoMergeSchema, + repositorySchema, + teamSchema, + userSchema, +} from "./shared"; + +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/integrations/github/src/schemas/pushes.ts b/integrations/github/src/schemas/pushes.ts new file mode 100644 index 000000000..d19965004 --- /dev/null +++ b/integrations/github/src/schemas/pushes.ts @@ -0,0 +1,27 @@ +import { z } from "zod"; +import { + repositorySchema, + userSchema, + installationLiteSchema, + organizationSchema, + commitSchema, + committerSchema, +} from "./shared"; + +export const pushEventSchema = z.object({ + ref: z.string(), + before: z.string(), + after: z.string(), + created: z.boolean(), + deleted: z.boolean(), + forced: z.boolean(), + base_ref: z.string().nullable(), + compare: z.string(), + commits: z.array(commitSchema), + head_commit: commitSchema.nullable(), + repository: repositorySchema, + pusher: committerSchema, + sender: userSchema, + installation: installationLiteSchema.optional(), + organization: organizationSchema.optional(), +}); diff --git a/integrations/github/src/schemas/shared.ts b/integrations/github/src/schemas/shared.ts new file mode 100644 index 000000000..d57901baa --- /dev/null +++ b/integrations/github/src/schemas/shared.ts @@ -0,0 +1,507 @@ +import { z } from "zod"; + +export const userSchema = z.object({ + login: z.string(), + id: z.number(), + node_id: z.string(), + name: z.string().optional(), + email: z.string().optional().nullable(), + avatar_url: z.string(), + gravatar_id: z.string(), + url: z.string(), + html_url: z.string(), + followers_url: z.string(), + following_url: z.string(), + gists_url: z.string(), + starred_url: z.string(), + subscriptions_url: z.string(), + organizations_url: z.string(), + repos_url: z.string(), + events_url: z.string(), + received_events_url: z.string(), + type: z.union([ + z.literal("Bot"), + z.literal("User"), + z.literal("Organization"), + ]), + site_admin: z.boolean(), +}); + +export const licenseSchema = z.object({ + key: z.string(), + name: z.string(), + spdx_id: z.string(), + url: z.string().nullable(), + node_id: z.string(), +}); + +export const installationLiteSchema = z.object({ + id: z.number(), + node_id: z.string(), +}); + +export const organizationSchema = z.object({ + login: z.string(), + id: z.number(), + node_id: z.string(), + url: z.string(), + html_url: z.string().optional(), + repos_url: z.string(), + events_url: z.string(), + hooks_url: z.string(), + issues_url: z.string(), + members_url: z.string(), + public_members_url: z.string(), + avatar_url: z.string(), + description: z.string().nullable(), +}); + +export const repositorySchema = z.object({ + id: z.number(), + node_id: z.string(), + name: z.string(), + full_name: z.string(), + private: z.boolean(), + owner: userSchema, + html_url: z.string(), + description: z.string().nullable(), + fork: z.boolean(), + url: z.string(), + forks_url: z.string(), + keys_url: z.string(), + collaborators_url: z.string(), + teams_url: z.string(), + hooks_url: z.string(), + issue_events_url: z.string(), + events_url: z.string(), + assignees_url: z.string(), + branches_url: z.string(), + tags_url: z.string(), + blobs_url: z.string(), + git_tags_url: z.string(), + git_refs_url: z.string(), + trees_url: z.string(), + statuses_url: z.string(), + languages_url: z.string(), + stargazers_url: z.string(), + contributors_url: z.string(), + subscribers_url: z.string(), + subscription_url: z.string(), + commits_url: z.string(), + git_commits_url: z.string(), + comments_url: z.string(), + issue_comment_url: z.string(), + contents_url: z.string(), + compare_url: z.string(), + merges_url: z.string(), + archive_url: z.string(), + downloads_url: z.string(), + issues_url: z.string(), + pulls_url: z.string(), + milestones_url: z.string(), + notifications_url: z.string(), + labels_url: z.string(), + releases_url: z.string(), + deployments_url: z.string(), + created_at: z.union([z.number(), z.string()]), + updated_at: z.string(), + pushed_at: z.union([z.number(), z.string()]).nullable(), + git_url: z.string(), + ssh_url: z.string(), + clone_url: z.string(), + svn_url: z.string(), + homepage: z.string().nullable(), + size: z.number(), + stargazers_count: z.number(), + watchers_count: z.number(), + language: z.string().nullable(), + has_issues: z.boolean(), + has_projects: z.boolean(), + has_downloads: z.boolean(), + has_wiki: z.boolean(), + has_pages: z.boolean(), + forks_count: z.number(), + mirror_url: z.string().nullable(), + archived: z.boolean(), + disabled: z.boolean().optional(), + open_issues_count: z.number(), + license: licenseSchema.nullable(), + forks: z.number(), + open_issues: z.number(), + watchers: z.number(), + stargazers: z.number().optional(), + default_branch: z.string(), + allow_squash_merge: z.boolean().optional(), + allow_merge_commit: z.boolean().optional(), + allow_rebase_merge: z.boolean().optional(), + allow_auto_merge: z.boolean().optional(), + allow_forking: z.boolean().optional(), + allow_update_branch: z.boolean().optional(), + use_squash_pr_title_as_default: z.boolean().optional(), + squash_merge_commit_message: z.string().optional(), + squash_merge_commit_title: z.string().optional(), + merge_commit_message: z.string().optional(), + merge_commit_title: z.string().optional(), + is_template: z.boolean(), + web_commit_signoff_required: z.boolean(), + topics: z.array(z.string()), + visibility: z.union([ + z.literal("public"), + z.literal("private"), + z.literal("internal"), + ]), + delete_branch_on_merge: z.boolean().optional(), + master_branch: z.string().optional(), + permissions: z + .object({ + pull: z.boolean(), + push: z.boolean(), + admin: z.boolean(), + maintain: z.boolean().optional(), + triage: z.boolean().optional(), + }) + .optional(), + public: z.boolean().optional(), + 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(), + node_id: z.string(), + owner: userSchema, + name: z.string(), + description: z.string().nullable(), + external_url: z.string(), + html_url: z.string(), + created_at: z.string(), + updated_at: z.string(), + permissions: z + .object({ + actions: z.union([z.literal("read"), z.literal("write")]).optional(), + administration: z + .union([z.literal("read"), z.literal("write")]) + .optional(), + blocking: z.union([z.literal("read"), z.literal("write")]).optional(), + checks: z.union([z.literal("read"), z.literal("write")]).optional(), + content_references: z + .union([z.literal("read"), z.literal("write")]) + .optional(), + contents: z.union([z.literal("read"), z.literal("write")]).optional(), + deployments: z.union([z.literal("read"), z.literal("write")]).optional(), + discussions: z.union([z.literal("read"), z.literal("write")]).optional(), + emails: z.union([z.literal("read"), z.literal("write")]).optional(), + environments: z.union([z.literal("read"), z.literal("write")]).optional(), + issues: z.union([z.literal("read"), z.literal("write")]).optional(), + keys: z.union([z.literal("read"), z.literal("write")]).optional(), + members: z.union([z.literal("read"), z.literal("write")]).optional(), + merge_queues: z.union([z.literal("read"), z.literal("write")]).optional(), + metadata: z.union([z.literal("read"), z.literal("write")]).optional(), + organization_administration: z + .union([z.literal("read"), z.literal("write")]) + .optional(), + organization_hooks: z + .union([z.literal("read"), z.literal("write")]) + .optional(), + organization_packages: z + .union([z.literal("read"), z.literal("write")]) + .optional(), + organization_plan: z + .union([z.literal("read"), z.literal("write")]) + .optional(), + organization_projects: z + .union([z.literal("read"), z.literal("write")]) + .optional(), + organization_secrets: z + .union([z.literal("read"), z.literal("write")]) + .optional(), + organization_self_hosted_runners: z + .union([z.literal("read"), z.literal("write")]) + .optional(), + organization_user_blocking: z + .union([z.literal("read"), z.literal("write")]) + .optional(), + packages: z.union([z.literal("read"), z.literal("write")]).optional(), + pages: z.union([z.literal("read"), z.literal("write")]).optional(), + pull_requests: z + .union([z.literal("read"), z.literal("write")]) + .optional(), + repository_hooks: z + .union([z.literal("read"), z.literal("write")]) + .optional(), + repository_projects: z + .union([z.literal("read"), z.literal("write")]) + .optional(), + secret_scanning_alerts: z + .union([z.literal("read"), z.literal("write")]) + .optional(), + secrets: z.union([z.literal("read"), z.literal("write")]).optional(), + security_events: z + .union([z.literal("read"), z.literal("write")]) + .optional(), + security_scanning_alert: z + .union([z.literal("read"), z.literal("write")]) + .optional(), + single_file: z.union([z.literal("read"), z.literal("write")]).optional(), + statuses: z.union([z.literal("read"), z.literal("write")]).optional(), + team_discussions: z + .union([z.literal("read"), z.literal("write")]) + .optional(), + vulnerability_alerts: z + .union([z.literal("read"), z.literal("write")]) + .optional(), + workflows: z.union([z.literal("read"), z.literal("write")]).optional(), + }) + .optional(), + events: z + .array( + z.union([ + z.literal("branch_protection_rule"), + z.literal("check_run"), + z.literal("check_suite"), + z.literal("code_scanning_alert"), + z.literal("commit_comment"), + z.literal("content_reference"), + z.literal("create"), + z.literal("delete"), + z.literal("deployment"), + z.literal("deployment_review"), + z.literal("deployment_status"), + z.literal("deploy_key"), + z.literal("discussion"), + z.literal("discussion_comment"), + z.literal("fork"), + z.literal("gollum"), + z.literal("issues"), + z.literal("issue_comment"), + z.literal("label"), + z.literal("member"), + z.literal("membership"), + z.literal("merge_group"), + z.literal("merge_queue_entry"), + z.literal("milestone"), + z.literal("organization"), + z.literal("org_block"), + z.literal("page_build"), + z.literal("project"), + z.literal("projects_v2_item"), + z.literal("project_card"), + z.literal("project_column"), + z.literal("public"), + z.literal("pull_request"), + z.literal("pull_request_review"), + z.literal("pull_request_review_comment"), + z.literal("push"), + z.literal("registry_package"), + z.literal("release"), + z.literal("repository"), + z.literal("repository_dispatch"), + z.literal("secret_scanning_alert"), + z.literal("secret_scanning_alert_location"), + z.literal("security_and_analysis"), + z.literal("star"), + z.literal("status"), + z.literal("team"), + z.literal("team_add"), + z.literal("watch"), + z.literal("workflow_dispatch"), + z.literal("workflow_run"), + z.literal("workflow_job"), + ]) + ) + .optional(), +}); + +export const reactionsSchema = z.object({ + url: z.string(), + total_count: z.number(), + "+1": z.number(), + "-1": z.number(), + laugh: z.number(), + hooray: z.number(), + confused: z.number(), + heart: z.number(), + rocket: z.number(), + eyes: z.number(), +}); + +export const authorAssociationSchema = z.union([ + z.literal("COLLABORATOR"), + z.literal("CONTRIBUTOR"), + z.literal("FIRST_TIMER"), + z.literal("FIRST_TIME_CONTRIBUTOR"), + z.literal("MANNEQUIN"), + z.literal("MEMBER"), + z.literal("NONE"), + z.literal("OWNER"), +]); + +export const milestoneSchema = z.object({ + url: z.string(), + html_url: z.string(), + labels_url: z.string(), + id: z.number(), + node_id: z.string(), + number: z.number(), + title: z.string(), + description: z.string().nullable(), + creator: userSchema, + open_issues: z.number(), + closed_issues: z.number(), + state: z.union([z.literal("open"), z.literal("closed")]), + created_at: z.string(), + updated_at: z.string(), + due_on: z.string().nullable(), + closed_at: z.string().nullable(), +}); + +export const labelSchema = z.object({ + id: z.number(), + node_id: z.string(), + url: z.string(), + name: z.string(), + description: z.string().nullable(), + color: z.string(), + default: z.boolean(), +}); + +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 simplePullRequestSchema = 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(), + draft: z.boolean(), + 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(), +}); + +export const committerSchema = z.object({ + name: z.string(), + email: z.string().nullable(), + date: z.string().optional(), + username: z.string().optional(), +}); + +export const commitSchema = z.object({ + id: z.string(), + tree_id: z.string(), + distinct: z.boolean(), + message: z.string(), + timestamp: z.string(), + url: z.string(), + author: committerSchema, + committer: committerSchema, + added: z.array(z.string()), + modified: z.array(z.string()), + removed: z.array(z.string()), +}); diff --git a/integrations/github/src/schemas/stars.ts b/integrations/github/src/schemas/stars.ts new file mode 100644 index 000000000..490809391 --- /dev/null +++ b/integrations/github/src/schemas/stars.ts @@ -0,0 +1,22 @@ +import { z } from "zod"; +import { organizationSchema, repositorySchema, userSchema } from "./shared"; + +export const starCreatedEventSchema = z.object({ + action: z.literal("created"), + starred_at: z.coerce.date(), + repository: repositorySchema, + sender: userSchema, + organization: organizationSchema.optional(), +}); + +export const starDeletedEventSchema = z.object({ + action: z.literal("deleted"), + repository: repositorySchema, + sender: userSchema, + organization: organizationSchema.optional(), +}); + +export const starEventSchema = z.discriminatedUnion("action", [ + starCreatedEventSchema, + starDeletedEventSchema, +]); diff --git a/integrations/github/src/types.ts b/integrations/github/src/types.ts new file mode 100644 index 000000000..e69de29bb diff --git a/integrations/github/src/webhooks.ts b/integrations/github/src/webhooks.ts new file mode 100644 index 000000000..2277ce49c --- /dev/null +++ b/integrations/github/src/webhooks.ts @@ -0,0 +1,223 @@ +import { Webhooks } from "@octokit/webhooks"; +import crypto from "crypto"; +import { z } from "zod"; +import { getAccessToken } from "@trigger.dev/integration-sdk"; +import type { + DisplayProperty, + HandleWebhookOptions, + WebhookConfig, + WebhookIntegration, +} from "@trigger.dev/integration-sdk"; +import { + WebhookOrganizationSourceSchema, + WebhookRepoSourceSchema, + WebhookSourceSchema, +} from "./schemas"; + +export class GitHubWebhookIntegration implements WebhookIntegration { + keyForSource(source: unknown): string { + const githubSource = parseWebhookSource(source); + + if (githubSource.subresource === "repository") { + return `repository.${githubSource.repo}.${githubSource.events.join(".")}`; + } else if (githubSource.subresource === "organization") { + return `organization.${githubSource.org}`; + } else { + throw new Error(`Unknown subresource`); + } + } + + registerWebhook(config: WebhookConfig, source: unknown) { + const githubSource = parseWebhookSource(source); + + if (githubSource.subresource === "repository") { + return registerRepositoryWebhook(config, githubSource); + } else if (githubSource.subresource === "organization") { + return registerOrganizationWebhook(config, githubSource); + } else { + throw new Error(`Unknown subresource`); + } + } + + handleWebhookRequest(options: HandleWebhookOptions) { + const deliveryId = options.request.headers["x-github-delivery"]; + const hookId = options.request.headers["x-github-hook-id"]; + + const signature = options.request.headers["x-hub-signature-256"]; + + if (options.secret && signature) { + const githubWebhooks = new Webhooks({ + secret: options.secret, + }); + + console.log( + `[delivery=${deliveryId}][hook=${hookId}] GitHubWebhookIntegration: Verifying signature ${signature}` + ); + + if (!githubWebhooks.verify(options.request.body, signature)) { + return { + status: "error" as const, + error: `GitHubWebhookIntegration: Could not verify webhook payload, invalid signature or secret. [deliveryId = ${deliveryId}]]`, + }; + } + } + + const event = options.request.headers["x-github-event"]; + + const context = omit(options.request.headers, [ + "x-github-event", + "x-github-delivery", + "x-hub-signature-256", + "x-hub-signature", + "content-type", + "content-length", + "accept", + "accept-encoding", + "x-forwarded-proto", + ]); + + const id = md5Hash([hookId, deliveryId].join("-")); + + return { + status: "ok" as const, + data: { id, payload: options.request.body, event, context }, + }; + } + + displayProperties(source: unknown) { + const githubSource = parseWebhookSource(source); + + const title = `GitHub ${githubSource.events.join(", ")}`; + let properties: DisplayProperty[] = []; + + switch (githubSource.subresource) { + case "repository": { + properties = [ + { + key: "Repo", + value: githubSource.repo, + }, + ]; + break; + } + case "organization": { + properties = [ + { + key: "Organization", + value: githubSource.org, + }, + ]; + break; + } + default: { + throw new Error(`Unknown subresource`); + } + } + + return { title, properties }; + } +} + +export const webhooks = new GitHubWebhookIntegration(); + +async function registerRepositoryWebhook( + config: WebhookConfig, + source: z.infer +) { + // Create the webhook in github + const response = await fetch( + `https://api.github.com/repos/${source.repo}/hooks`, + { + method: "POST", + headers: { + "Content-Type": "application/json", + Accept: "application/vnd.github+json", + Authorization: `Bearer ${getAccessToken(config.accessInfo)}`, + "X-GitHub-Api-Version": "2022-11-28", + }, + body: JSON.stringify({ + name: "web", + active: true, + events: source.events, + config: { + url: config.callbackUrl, + content_type: "json", + secret: config.secret, + insecure_ssl: "0", + }, + }), + } + ); + + if (!response.ok) { + throw new Error(`Failed to register webhook: ${response.statusText}`); + } + + const webhook = await response.json(); + + return webhook; +} + +async function registerOrganizationWebhook( + config: WebhookConfig, + source: z.infer +) { + // Create the webhook in github + const response = await fetch( + `https://api.github.com/orgs/${source.org}/hooks`, + { + method: "POST", + headers: { + "Content-Type": "application/json", + Accept: "application/vnd.github+json", + Authorization: `Bearer ${getAccessToken(config.accessInfo)}`, + "X-GitHub-Api-Version": "2022-11-28", + }, + body: JSON.stringify({ + name: "web", + active: true, + events: source.events, + config: { + url: config.callbackUrl, + content_type: "json", + secret: config.secret, + insecure_ssl: "0", + }, + }), + } + ); + + if (!response.ok) { + throw new Error(`Failed to register webhook: ${response.statusText}`); + } + + const webhook = await response.json(); + + return webhook; +} + +function parseWebhookSource(source: unknown) { + return WebhookSourceSchema.parse(source); +} + +function omit, K extends keyof T>( + obj: T, + keys: K[] +): Omit { + const result: any = {}; + + for (const key of Object.keys(obj)) { + if (!keys.includes(key as K)) { + result[key] = obj[key]; + } + } + + return result; +} + +function md5Hash(str: string): string { + const hash = crypto.createHash("md5"); + hash.update(str); + + return hash.digest("hex"); +} diff --git a/integrations/github/tsconfig.json b/integrations/github/tsconfig.json new file mode 100644 index 000000000..8437d79f4 --- /dev/null +++ b/integrations/github/tsconfig.json @@ -0,0 +1,20 @@ +{ + "extends": "@trigger.dev/tsconfig/node16.json", + "include": ["./src/**/*.ts"], + "compilerOptions": { + "experimentalDecorators": true, + "emitDecoratorMetadata": true, + "lib": ["DOM", "DOM.Iterable", "ES2019"], + "paths": { + "@trigger.dev/sdk/*": ["../../packages/trigger-sdk/src/*"], + "@trigger.dev/sdk": ["../../packages/trigger-sdk/src/index"], + "@trigger.dev/integration-sdk/*": [ + "../../packages/integration-sdk/src/*" + ], + "@trigger.dev/integration-sdk": [ + "../../packages/integration-sdk/src/index" + ] + } + }, + "exclude": ["node_modules"] +} diff --git a/integrations/github/tsup.config.ts b/integrations/github/tsup.config.ts new file mode 100644 index 000000000..a71244deb --- /dev/null +++ b/integrations/github/tsup.config.ts @@ -0,0 +1,23 @@ +import { defineConfig } from "tsup"; + +export default defineConfig([ + { + name: "main", + entry: ["./src/index.ts"], + outDir: "./dist", + platform: "node", + format: ["cjs"], + legacyOutput: true, + sourcemap: true, + clean: true, + bundle: true, + splitting: false, + dts: true, + treeshake: { + preset: "smallest", + }, + external: ["http", "https", "util", "events", "tty", "os", "timers"], + esbuildPlugins: [], + noExternal: ["@trigger.dev/common-schemas"], + }, +]); diff --git a/packages/integration-sdk/README.md b/packages/integration-sdk/README.md new file mode 100644 index 000000000..22ddcc68b --- /dev/null +++ b/packages/integration-sdk/README.md @@ -0,0 +1 @@ +Docs coming soon. diff --git a/packages/integration-sdk/package.json b/packages/integration-sdk/package.json new file mode 100644 index 000000000..e65f2ea08 --- /dev/null +++ b/packages/integration-sdk/package.json @@ -0,0 +1,30 @@ +{ + "name": "@trigger.dev/integration-sdk", + "version": "0.1.16", + "description": "trigger.dev SDK for integrations", + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "publishConfig": { + "access": "public" + }, + "files": [ + "dist" + ], + "devDependencies": { + "@trigger.dev/tsconfig": "workspace:*", + "@types/debug": "^4.1.7", + "@types/node": "16", + "@types/react": "^18.0.21", + "rimraf": "^3.0.2", + "tsup": "^6.5.0" + }, + "scripts": { + "clean": "rimraf dist", + "build": "npm run clean && npm run build:tsup", + "build:tsup": "tsup" + }, + "dependencies": { + "debug": "^4.3.4", + "zod": "^3.20.2" + } +} \ No newline at end of file diff --git a/packages/integration-sdk/src/index.ts b/packages/integration-sdk/src/index.ts new file mode 100644 index 000000000..aebc4b7a1 --- /dev/null +++ b/packages/integration-sdk/src/index.ts @@ -0,0 +1,15 @@ +export * from "./types"; +export * from "./services"; + +import { AccessInfo } from "./types"; + +export function getAccessToken(accessInfo: AccessInfo): string { + switch (accessInfo.type) { + case "oauth2": { + return accessInfo.accessToken; + } + case "api_key": { + return accessInfo.api_key; + } + } +} diff --git a/packages/integration-sdk/src/services/index.ts b/packages/integration-sdk/src/services/index.ts new file mode 100644 index 000000000..4985b159c --- /dev/null +++ b/packages/integration-sdk/src/services/index.ts @@ -0,0 +1,137 @@ +import { z } from "zod"; +import debug from "debug"; + +const log = debug("trigger:integrations:services"); + +export type HttpServiceOptions = { + accessToken: string; + baseUrl: string; +}; + +export type HttpResponse< + TResponseSchema extends z.ZodTypeAny, + TErrorResponseSchema extends z.ZodTypeAny = z.AnyZodObject +> = + | { + success: true; + statusCode: number; + headers: Record; + data: z.infer; + } + | { + success: false; + statusCode: number; + headers: Record; + error?: z.infer; + }; + +export class HttpService { + constructor(private readonly options: HttpServiceOptions) {} + + async performRequest< + TResponseSchema extends z.ZodTypeAny, + TBodySchema extends z.ZodTypeAny = z.ZodUndefined + >( + endpoint: HttpEndpoint, + body?: z.infer + ): Promise> { + const url = `${this.options.baseUrl}${endpoint.options.path}`; + const method = endpoint.options.method; + const headers = { + Authorization: `Bearer ${this.options.accessToken}`, + "Content-Type": "application/json; charset=utf-8", + }; + const actualBody = body ? JSON.stringify(body) : undefined; + + const response = await fetch(url, { + method, + headers, + body: actualBody, + }); + + log( + "%s%s response %d", + this.options.baseUrl, + endpoint.options.path, + response.status + ); + + const json = await this.#safeGetJson(response); + + if (json) { + log( + "%s%s response %O", + this.options.baseUrl, + endpoint.options.path, + json + ); + + const parsedJson = endpoint.options.response.safeParse(json); + + if (parsedJson.success) { + return { + success: true, + data: parsedJson.data, + statusCode: response.status, + headers: normalizeHeaders(response.headers), + }; + } + + log( + "response json failed to parse %O, errors: %O", + parsedJson, + parsedJson.error + ); + + return { + success: false, + statusCode: response.status, + headers: normalizeHeaders(response.headers), + error: { type: "zod-parse", issues: parsedJson.error.issues }, + }; + } + + return { + success: false, + statusCode: response.status, + headers: normalizeHeaders(response.headers), + }; + } + + #safeGetJson = async (response: Response) => { + try { + return await response.json(); + } catch (error) { + return undefined; + } + }; +} + +export type HttpEndpointOptions< + TResponseSchema extends z.ZodTypeAny, + TBodySchema extends z.ZodTypeAny = z.ZodUndefined +> = { + response: TResponseSchema; + body?: TBodySchema; + method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE"; + path: string; +}; + +export class HttpEndpoint< + TResponseSchema extends z.ZodTypeAny, + TBodySchema extends z.ZodTypeAny = z.ZodUndefined +> { + constructor( + public options: HttpEndpointOptions + ) {} +} + +function normalizeHeaders(headers: Headers): Record { + const normalizedHeaders: Record = {}; + + headers.forEach((value, key) => { + normalizedHeaders[key.toLowerCase()] = value; + }); + + return normalizedHeaders; +} diff --git a/packages/integration-sdk/src/types.ts b/packages/integration-sdk/src/types.ts new file mode 100644 index 000000000..0bd63aa5b --- /dev/null +++ b/packages/integration-sdk/src/types.ts @@ -0,0 +1,91 @@ +import type { ReactNode } from "react"; + +export type AccessInfo = + | { type: "oauth2"; accessToken: string } + | { + type: "api_key"; + api_key: string; + additionalFields?: Record; + }; + +export interface WebhookConfig { + accessInfo: AccessInfo; + callbackUrl: string; + secret: string; +} + +export interface NormalizedRequest { + rawBody: string; + body: any; + headers: Record; + searchParams: URLSearchParams; +} + +export interface NormalizedResponse { + output: NonNullable; + context: any; +} + +export interface HandleWebhookOptions { + request: NormalizedRequest; + secret?: string; +} + +export interface ReceivedWebhook { + id: string; + event: string; + payload: any; + timestamp?: string; + context?: any; +} + +export type PerformRequestOptions = { + accessInfo: AccessInfo; + endpoint: string; + params: any; + cache?: CacheService; + metadata?: Record; +}; + +export type DisplayProperties = { + title: string; + properties?: DisplayProperty[]; +}; + +export type DisplayProperty = { key: string; value: string | number | boolean }; + +export interface PerformedRequestResponse { + response: NormalizedResponse; + isRetryable: boolean; + ok: boolean; +} + +export interface RequestIntegration { + perform: ( + options: PerformRequestOptions + ) => Promise; + displayProperties: (endpoint: string, params: any) => DisplayProperties; + renderComponent(input: any, output: any): ReactNode; +} + +export interface WebhookIntegration { + keyForSource: (source: unknown) => string; + registerWebhook: (config: WebhookConfig, source: unknown) => Promise; + handleWebhookRequest: ( + options: HandleWebhookOptions + ) => + | { status: "ok"; data: ReceivedWebhook } + | { status: "ignored"; reason: string } + | { status: "error"; error: string }; + displayProperties: (source: unknown) => DisplayProperties; +} + +export interface CacheService { + get: (key: string) => Promise; + set: (key: string, value: string, ttl?: number) => Promise; +} + +export type InternalIntegration = { + requests?: RequestIntegration; + webhooks?: WebhookIntegration; +}; diff --git a/packages/integration-sdk/tsconfig.json b/packages/integration-sdk/tsconfig.json new file mode 100644 index 000000000..4388aa73a --- /dev/null +++ b/packages/integration-sdk/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "@trigger.dev/tsconfig/node16.json", + "include": ["./src/**/*.ts", "tsup.config.ts"], + "compilerOptions": { + "experimentalDecorators": true, + "emitDecoratorMetadata": true, + "lib": ["DOM", "DOM.Iterable", "ES2021"] + }, + "exclude": ["node_modules"] +} diff --git a/packages/integration-sdk/tsup.config.ts b/packages/integration-sdk/tsup.config.ts new file mode 100644 index 000000000..74c4f4dc1 --- /dev/null +++ b/packages/integration-sdk/tsup.config.ts @@ -0,0 +1,19 @@ +import { defineConfig } from "tsup"; + +export default defineConfig([ + { + name: "main", + entry: ["./src/index.ts"], + outDir: "./dist", + platform: "node", + format: ["cjs"], + legacyOutput: true, + sourcemap: true, + clean: true, + bundle: true, + splitting: false, + dts: true, + external: ["http", "https", "util", "events", "tty", "os", "timers"], + esbuildPlugins: [], + }, +]); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7e2455f4e..f05255538 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -75,6 +75,8 @@ importers: '@testing-library/react': ^13.4.0 '@testing-library/user-event': ^14.4.3 '@trigger.dev/common-schemas': workspace:* + '@trigger.dev/github': workspace:* + '@trigger.dev/integration-sdk': workspace:* '@trigger.dev/providers': workspace:* '@trigger.dev/tailwind-config': workspace:* '@typeform/embed-react': ^2.14.1 @@ -184,7 +186,7 @@ importers: '@aws-sdk/client-s3': 3.245.0 '@aws-sdk/s3-request-presigner': 3.245.0 '@cfworker/json-schema': 1.12.5 - '@codemirror/autocomplete': 6.4.0_eo6pz6bvsllvatnnwfprpuflde + '@codemirror/autocomplete': 6.4.0_czcfkg2f66rxeiodoti7r2gulu '@codemirror/commands': 6.1.3 '@codemirror/lang-javascript': 6.1.2 '@codemirror/lang-json': 6.0.1 @@ -208,9 +210,10 @@ importers: '@tailwindcss/forms': 0.5.3_tailwindcss@3.1.8 '@tanstack/react-table': 8.7.6_biqbaboplfbrettd7655fr4n2y '@trigger.dev/common-schemas': link:../../packages/common-schemas + '@trigger.dev/github': link:../../integrations/github '@trigger.dev/providers': link:../../packages/trigger-providers '@typeform/embed-react': 2.14.1_react@18.2.0 - '@uiw/react-codemirror': 4.19.5_aguurb4bmecpxzejz52amioxne + '@uiw/react-codemirror': 4.19.5_k4ec5g7vuuzzonc3d6xbjnmmle bcryptjs: 2.4.3 classnames: 2.3.2 clsx: 1.2.1 @@ -277,6 +280,7 @@ importers: '@testing-library/jest-dom': 5.16.5 '@testing-library/react': 13.4.0_biqbaboplfbrettd7655fr4n2y '@testing-library/user-event': 14.4.3_ua4wuun3fnehcjqtqj2e2ldgpe + '@trigger.dev/integration-sdk': link:../../packages/integration-sdk '@trigger.dev/tailwind-config': link:../../config-packages/tailwind-config '@types/bcryptjs': 2.4.2 '@types/compression': 1.7.2 @@ -406,14 +410,14 @@ importers: examples/github-webhook: specifiers: - '@trigger.dev/integrations': workspace:* + '@trigger.dev/github': workspace:* '@trigger.dev/sdk': workspace:* '@trigger.dev/tsconfig': workspace:* '@types/node': '16' tsx: ^3.12.0 zod: ^3.20.2 dependencies: - '@trigger.dev/integrations': link:../../packages/trigger-integrations + '@trigger.dev/github': link:../../integrations/github '@trigger.dev/sdk': link:../../packages/trigger-sdk zod: 3.20.2 devDependencies: @@ -557,6 +561,27 @@ importers: '@types/node': 16.18.11 tsx: 3.12.2 + integrations/github: + specifiers: + '@octokit/webhooks': ^10.4.0 + '@trigger.dev/integration-sdk': workspace:* + '@trigger.dev/sdk': workspace:* + '@trigger.dev/tsconfig': workspace:* + '@types/node': '16' + rimraf: ^3.0.2 + tsup: ^6.5.0 + zod: ^3.20.2 + dependencies: + '@octokit/webhooks': 10.5.1 + zod: 3.20.2 + devDependencies: + '@trigger.dev/integration-sdk': link:../../packages/integration-sdk + '@trigger.dev/sdk': link:../../packages/trigger-sdk + '@trigger.dev/tsconfig': link:../../config-packages/tsconfig + '@types/node': 16.18.11 + rimraf: 3.0.2 + tsup: 6.5.0 + packages/common-schemas: specifiers: '@trigger.dev/tsconfig': workspace:* @@ -613,6 +638,27 @@ importers: '@trigger.dev/tsconfig': link:../../config-packages/tsconfig '@types/react': 18.0.26 + packages/integration-sdk: + specifiers: + '@trigger.dev/tsconfig': workspace:* + '@types/debug': ^4.1.7 + '@types/node': '16' + '@types/react': ^18.0.21 + debug: ^4.3.4 + rimraf: ^3.0.2 + tsup: ^6.5.0 + zod: ^3.20.2 + dependencies: + debug: 4.3.4 + zod: 3.20.2 + devDependencies: + '@trigger.dev/tsconfig': link:../../config-packages/tsconfig + '@types/debug': 4.1.7 + '@types/node': 16.18.11 + '@types/react': 18.0.26 + rimraf: 3.0.2 + tsup: 6.5.0 + packages/internal-bridge: specifiers: '@trigger.dev/common-schemas': workspace:* @@ -750,7 +796,7 @@ importers: tsup: ^6.5.0 zod: ^3.20.2 dependencies: - '@react-email/render': 0.0.3_react@18.2.0 + '@react-email/render': 0.0.3 '@trigger.dev/providers': link:../trigger-providers '@trigger.dev/sdk': link:../trigger-sdk zod: 3.20.2 @@ -3457,13 +3503,12 @@ packages: prettier: 2.8.2 dev: false - /@codemirror/autocomplete/6.4.0_eo6pz6bvsllvatnnwfprpuflde: + /@codemirror/autocomplete/6.4.0_czcfkg2f66rxeiodoti7r2gulu: resolution: {integrity: sha512-HLF2PnZAm1s4kGs30EiqKMgD7XsYaQ0XJnMR0rofEWQ5t5D60SfqpDIkIh1ze5tiEbyUWm8+VJ6W1/erVvBMIA==} peerDependencies: '@codemirror/language': ^6.0.0 '@codemirror/state': ^6.0.0 '@codemirror/view': ^6.0.0 - '@lezer/common': ^1.0.0 dependencies: '@codemirror/language': 6.3.2 '@codemirror/state': 6.2.0 @@ -3483,7 +3528,7 @@ packages: /@codemirror/lang-javascript/6.1.2: resolution: {integrity: sha512-OcwLfZXdQ1OHrLiIcKCn7MqZ7nx205CMKlhe+vL88pe2ymhT9+2P+QhwkYGxMICj8TDHyp8HFKVwpiisUT7iEQ==} dependencies: - '@codemirror/autocomplete': 6.4.0_eo6pz6bvsllvatnnwfprpuflde + '@codemirror/autocomplete': 6.4.0_czcfkg2f66rxeiodoti7r2gulu '@codemirror/language': 6.3.2 '@codemirror/lint': 6.1.0 '@codemirror/state': 6.2.0 @@ -4681,6 +4726,16 @@ packages: react-dom: 18.2.0_react@18.2.0 dev: false + /@react-email/render/0.0.3: + resolution: {integrity: sha512-+4eOrLGdTCJjoJU3PunekErjo3PFnhSDFjVINBHrfJT+1wlVdhWfDo7hFdzXJx/JOOYqmnZTilU/umGLdRumKQ==} + engines: {node: '>=18.0.0'} + dependencies: + pretty: 2.0.0 + react-dom: 18.2.0 + transitivePeerDependencies: + - react + dev: false + /@react-email/render/0.0.3_react@18.2.0: resolution: {integrity: sha512-+4eOrLGdTCJjoJU3PunekErjo3PFnhSDFjVINBHrfJT+1wlVdhWfDo7hFdzXJx/JOOYqmnZTilU/umGLdRumKQ==} engines: {node: '>=18.0.0'} @@ -4796,7 +4851,7 @@ packages: eslint: 8.31.0 eslint-import-resolver-node: 0.3.6 eslint-import-resolver-typescript: 3.5.3_hnftvkj7qg3s6bbigj4pr6djxy - eslint-plugin-import: 2.27.4_qdjeohovcytra7xto5vgmxssaq + eslint-plugin-import: 2.27.4_2ac3tknkazjoq5fxmuugu665ny eslint-plugin-jest: 26.9.0_ohsifnwenhmxgcp7mend4dnv74 eslint-plugin-jest-dom: 4.0.3_eslint@8.31.0 eslint-plugin-jsx-a11y: 6.7.1_eslint@8.31.0 @@ -5932,18 +5987,17 @@ packages: eslint-visitor-keys: 3.3.0 dev: true - /@uiw/codemirror-extensions-basic-setup/4.19.5_tbeldtdcrf45b35pezgkzq2u4e: + /@uiw/codemirror-extensions-basic-setup/4.19.5_wd2tsis3in55bkaiwnc2c46tom: resolution: {integrity: sha512-1zt7ZPJ01xKkSW/KDy0FZNga0bngN1fC594wCVG7FBi60ehfcAucpooQ+JSPScKXopxcb+ugPKZvVLzr9/OfzA==} peerDependencies: '@codemirror/autocomplete': '>=6.0.0' '@codemirror/commands': '>=6.0.0' '@codemirror/language': '>=6.0.0' - '@codemirror/lint': '>=6.0.0' '@codemirror/search': '>=6.0.0' '@codemirror/state': '>=6.0.0' '@codemirror/view': '>=6.0.0' dependencies: - '@codemirror/autocomplete': 6.4.0_eo6pz6bvsllvatnnwfprpuflde + '@codemirror/autocomplete': 6.4.0_czcfkg2f66rxeiodoti7r2gulu '@codemirror/commands': 6.1.3 '@codemirror/language': 6.3.2 '@codemirror/lint': 6.1.0 @@ -5952,14 +6006,11 @@ packages: '@codemirror/view': 6.7.2 dev: false - /@uiw/react-codemirror/4.19.5_aguurb4bmecpxzejz52amioxne: + /@uiw/react-codemirror/4.19.5_k4ec5g7vuuzzonc3d6xbjnmmle: resolution: {integrity: sha512-ZCHh8d7beXbF8/t7F1+yHht6A9Y6CdKeOkZq4A09lxJEnyTQrj1FMf2zvfaqc7K23KNjkTCtSlbqKKbVDgrWaw==} peerDependencies: - '@babel/runtime': '>=7.11.0' '@codemirror/state': '>=6.0.0' - '@codemirror/theme-one-dark': '>=6.0.0' '@codemirror/view': '>=6.0.0' - codemirror: '>=6.0.0' react: '>=16.8.0' react-dom: '>=16.8.0' dependencies: @@ -5968,14 +6019,13 @@ packages: '@codemirror/state': 6.2.0 '@codemirror/theme-one-dark': 6.1.0 '@codemirror/view': 6.7.2 - '@uiw/codemirror-extensions-basic-setup': 4.19.5_tbeldtdcrf45b35pezgkzq2u4e - codemirror: 6.0.1_@lezer+common@1.0.2 + '@uiw/codemirror-extensions-basic-setup': 4.19.5_wd2tsis3in55bkaiwnc2c46tom + codemirror: 6.0.1 react: 18.2.0 react-dom: 18.2.0_react@18.2.0 transitivePeerDependencies: - '@codemirror/autocomplete' - '@codemirror/language' - - '@codemirror/lint' - '@codemirror/search' dev: false @@ -7286,18 +7336,16 @@ packages: engines: {node: '>=0.10.0'} dev: false - /codemirror/6.0.1_@lezer+common@1.0.2: + /codemirror/6.0.1: resolution: {integrity: sha512-J8j+nZ+CdWmIeFIGXEFbFPtpiYacFMDR8GlHK3IyHQJMCaVRfGx9NT+Hxivv1ckLWPvNdZqndbr/7lVhrf/Svg==} dependencies: - '@codemirror/autocomplete': 6.4.0_eo6pz6bvsllvatnnwfprpuflde + '@codemirror/autocomplete': 6.4.0_czcfkg2f66rxeiodoti7r2gulu '@codemirror/commands': 6.1.3 '@codemirror/language': 6.3.2 '@codemirror/lint': 6.1.0 '@codemirror/search': 6.2.3 '@codemirror/state': 6.2.0 '@codemirror/view': 6.7.2 - transitivePeerDependencies: - - '@lezer/common' dev: false /collection-visit/1.0.0: @@ -8633,7 +8681,7 @@ packages: debug: 4.3.4 enhanced-resolve: 5.12.0 eslint: 8.31.0 - eslint-plugin-import: 2.27.4_qdjeohovcytra7xto5vgmxssaq + eslint-plugin-import: 2.27.4_2ac3tknkazjoq5fxmuugu665ny get-tsconfig: 4.3.0 globby: 13.1.3 is-core-module: 2.11.0 @@ -8643,7 +8691,7 @@ packages: - supports-color dev: true - /eslint-module-utils/2.7.4_sqt5xxn4ciiurbqrzlaarm6ama: + /eslint-module-utils/2.7.4_v73lhamtbyinynmwa5fn7kpmfq: resolution: {integrity: sha512-j4GT+rqzCoRKHwURX7pddtIPGySnX9Si/cgMI5ztrcqOPtk5dDEeZ34CQVPphnqkJytlc97Vuk05Um2mJ3gEQA==} engines: {node: '>=4'} peerDependencies: @@ -8668,6 +8716,7 @@ packages: debug: 3.2.7 eslint: 8.31.0 eslint-import-resolver-node: 0.3.7 + eslint-import-resolver-typescript: 3.5.3_hnftvkj7qg3s6bbigj4pr6djxy transitivePeerDependencies: - supports-color dev: true @@ -8692,7 +8741,7 @@ packages: regexpp: 3.2.0 dev: true - /eslint-plugin-import/2.27.4_qdjeohovcytra7xto5vgmxssaq: + /eslint-plugin-import/2.27.4_2ac3tknkazjoq5fxmuugu665ny: resolution: {integrity: sha512-Z1jVt1EGKia1X9CnBCkpAOhWy8FgQ7OmJ/IblEkT82yrFU/xJaxwujaTzLWqigewwynRQ9mmHfX9MtAfhxm0sA==} engines: {node: '>=4'} peerDependencies: @@ -8710,7 +8759,7 @@ packages: doctrine: 2.1.0 eslint: 8.31.0 eslint-import-resolver-node: 0.3.7 - eslint-module-utils: 2.7.4_sqt5xxn4ciiurbqrzlaarm6ama + eslint-module-utils: 2.7.4_v73lhamtbyinynmwa5fn7kpmfq has: 1.0.3 is-core-module: 2.11.0 is-glob: 4.0.3 @@ -13810,6 +13859,15 @@ packages: shallow-equal: 1.2.1 dev: false + /react-dom/18.2.0: + resolution: {integrity: sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==} + peerDependencies: + react: ^18.2.0 + dependencies: + loose-envify: 1.4.0 + scheduler: 0.23.0 + dev: false + /react-dom/18.2.0_react@18.2.0: resolution: {integrity: sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==} peerDependencies: diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index c54dbc83e..ad67a9919 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,5 +1,6 @@ packages: - "config-packages/*" - "packages/*" + - "integrations/*" - "apps/**" - "examples/*"