From 89f7de7317865d223ea28c6f5779c65f69c79eb5 Mon Sep 17 00:00:00 2001 From: whilefoo Date: Wed, 7 Feb 2024 22:06:42 +0100 Subject: [PATCH] feat: config event handler --- bun.lockb | Bin 303588 -> 303612 bytes package.json | 3 +- src/github/github-event-handler.ts | 34 ++-- src/github/handlers/index.ts | 37 +++++ src/github/types/config.ts | 25 +++ src/github/types/github-events.ts | 248 +++++++++++++++++++++++++++++ src/github/utils/config.ts | 53 ++++++ 7 files changed, 383 insertions(+), 17 deletions(-) create mode 100644 src/github/types/config.ts create mode 100644 src/github/types/github-events.ts create mode 100644 src/github/utils/config.ts diff --git a/bun.lockb b/bun.lockb index 3776951a9c5c597a91b09ae53f514636af5dc201..12ce21dbdaef808f94262e81e7551012681b86e7 100755 GIT binary patch delta 31976 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 delta 31771 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 diff --git a/package.json b/package.json index 979971c..ebf3840 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,8 @@ "create-cloudflare": "^2.8.3", "octokit": "^3.1.2", "smee-client": "^2.0.0", - "universal-github-app-jwt": "^2.0.5" + "universal-github-app-jwt": "^2.0.5", + "yaml": "^2.3.4" }, "devDependencies": { "@cloudflare/workers-types": "^4.20240117.0", diff --git a/src/github/github-event-handler.ts b/src/github/github-event-handler.ts index fbbb2d5..fc1a1dc 100644 --- a/src/github/github-event-handler.ts +++ b/src/github/github-event-handler.ts @@ -1,4 +1,4 @@ -import { Webhooks } from "@octokit/webhooks"; +import { EmitterWebhookEvent, Webhooks } from "@octokit/webhooks"; import { customOctokit } from "./github-client"; import { GitHubContext, SimplifiedContext } from "./github-context"; @@ -25,21 +25,7 @@ export class GitHubEventHandler { this.webhooks = new Webhooks({ secret: this._webhookSecret, - transform: (event) => { - let installationId: number | undefined = undefined; - if ("installation" in event.payload) { - installationId = event.payload.installation?.id; - } - const octokit = new customOctokit({ - auth: { - appId: this._appId, - privateKey: this._privateKey, - installationId: installationId, - }, - }); - - return new GitHubContext(event, octokit); - }, + transform: this.transformEvent, }); this.on = this.webhooks.on; @@ -53,4 +39,20 @@ export class GitHubEventHandler { console.error(error); }); } + + transformEvent(event: EmitterWebhookEvent) { + let installationId: number | undefined = undefined; + if ("installation" in event.payload) { + installationId = event.payload.installation?.id; + } + const octokit = new customOctokit({ + auth: { + appId: this._appId, + privateKey: this._privateKey, + installationId: installationId, + }, + }); + + return new GitHubContext(event, octokit); + } } diff --git a/src/github/handlers/index.ts b/src/github/handlers/index.ts index f349638..34ae1a7 100644 --- a/src/github/handlers/index.ts +++ b/src/github/handlers/index.ts @@ -1,6 +1,43 @@ +import { EmitterWebhookEvent } from "@octokit/webhooks"; import { GitHubEventHandler } from "../github-event-handler"; +import { getConfig } from "../utils/config"; import { issueCommentCreated } from "./issue-comment/created"; export function bindHandlers(webhooks: GitHubEventHandler) { webhooks.on("issue_comment.created", issueCommentCreated); + webhooks.onAny( + tryCatchWrapper(async (event) => { + const context = webhooks.transformEvent(event); + + const config = await getConfig(context); + + if (!config) { + console.log("No config found"); + return; + } + + const handler = config.handlers.events[event.name]; + + if (handler.length === 0) { + console.log(`No handler found for event ${event.name}`); + return; + } + + for (const { workflow } of handler) { + console.log(`Calling handler for event ${event.name} and workflow ${workflow}`); + + // TODO: call the workflow + } + }) + ); +} + +function tryCatchWrapper(fn: (event: EmitterWebhookEvent) => unknown) { + return async (event: EmitterWebhookEvent) => { + try { + await fn(event); + } catch (error) { + console.error("Error in event handler", error); + } + }; } diff --git a/src/github/types/config.ts b/src/github/types/config.ts new file mode 100644 index 0000000..cb79043 --- /dev/null +++ b/src/github/types/config.ts @@ -0,0 +1,25 @@ +import { Type as T } from "@sinclair/typebox"; +import { StaticDecode } from "@sinclair/typebox"; +import { GitHubEvent } from "./github-events"; + +enum Commands { + Start = "start", + Stop = "stop", +} + +const handlerSchema = T.Array( + T.Object({ + workflow: T.String(), + settings: T.Unknown(), + }), + { default: [] } +); + +export const configSchema = T.Object({ + handlers: T.Object({ + commands: T.Record(T.Enum(Commands), handlerSchema, { default: {} }), + events: T.Record(T.Enum(GitHubEvent), handlerSchema, { default: {} }), + }), +}); + +export type Config = StaticDecode; diff --git a/src/github/types/github-events.ts b/src/github/types/github-events.ts new file mode 100644 index 0000000..1ddca5e --- /dev/null +++ b/src/github/types/github-events.ts @@ -0,0 +1,248 @@ +export enum GitHubEvent { + "BRANCH_PROTECTION_RULE" = "branch_protection_rule", + "BRANCH_PROTECTION_RULE_CREATED" = "branch_protection_rule.created", + "BRANCH_PROTECTION_RULE_DELETED" = "branch_protection_rule.deleted", + "BRANCH_PROTECTION_RULE_EDITED" = "branch_protection_rule.edited", + "CHECK_RUN" = "check_run", + "CHECK_RUN_COMPLETED" = "check_run.completed", + "CHECK_RUN_CREATED" = "check_run.created", + "CHECK_RUN_REQUESTED_ACTION" = "check_run.requested_action", + "CHECK_RUN_REREQUESTED" = "check_run.rerequested", + "CHECK_SUITE" = "check_suite", + "CHECK_SUITE_COMPLETED" = "check_suite.completed", + "CHECK_SUITE_REQUESTED" = "check_suite.requested", + "CHECK_SUITE_fREREQUESTED" = "check_suite.rerequested", + "CODE_SCANNING_ALERT" = "code_scanning_alert", + "CODE_SCANNING_ALERT_APPEARED_IN_BRANCH" = "code_scanning_alert.appeared_in_branch", + "CODE_SCANNING_ALERT_CLOSED_BY_USER" = "code_scanning_alert.closed_by_user", + "CODE_SCANNING_ALERT_CREATED" = "code_scanning_alert.created", + "CODE_SCANNING_ALERT_FIXED" = "code_scanning_alert.fixed", + "CODE_SCANNING_ALERT_REOPENED" = "code_scanning_alert.reopened", + "CODE_SCANNING_ALERT_REOPENED_BY_USER" = "code_scanning_alert.reopened_by_user", + "COMMIT_COMMENT" = "commit_comment", + "COMMIT_COMMENT_CREATED" = "commit_comment.created", + "CREATE" = "create", + "DELETE" = "delete", + "DEPLOY_KEY" = "deploy_key", + "DEPLOY_KEY_CREATED" = "deploy_key.created", + "DEPLOY_KEY_DELETED" = "deploy_key.deleted", + "DEPLOYMENT" = "deployment", + "DEPLOYMENT_CREATED" = "deployment.created", + "DEPLOYMENT_STATUS" = "deployment_status", + "DEPLOYMENT_STATUS_CREATED" = "deployment_status.created", + "DISCUSSION" = "discussion", + "DISCUSSION_ANSWERED" = "discussion.answered", + "DISCUSSION_CATEGORY_CHANGED" = "discussion.category_changed", + "DISCUSSION_CREATED" = "discussion.created", + "DISCUSSION_DELETED" = "discussion.deleted", + "DISCUSSION_EDITED" = "discussion.edited", + "DISCUSSION_LABELED" = "discussion.labeled", + "DISCUSSION_LOCKED" = "discussion.locked", + "DISCUSSION_PINNED" = "discussion.pinned", + "DISCUSSION_TRANSFERRED" = "discussion.transferred", + "DISCUSSION_UNANSWERED" = "discussion.unanswered", + "DISCUSSION_UNLABELED" = "discussion.unlabeled", + "DISCUSSION_UNLOCKED" = "discussion.unlocked", + "DISCUSSION_UNPINNED" = "discussion.unpinned", + "DISCUSSION_COMMENT" = "discussion_comment", + "DISCUSSION_COMMENT_CREATED" = "discussion_comment.created", + "DISCUSSION_COMMENT_DELETED" = "discussion_comment.deleted", + "DISCUSSION_COMMENT_EDITED" = "discussion_comment.edited", + "FORK" = "fork", + "GITHUB_APP_AUTHORIZATION" = "github_app_authorization", + "GITHUB_APP_AUTHORIZATION_REVOKED" = "github_app_authorization.revoked", + "GOLLUM" = "gollum", + "INSTALLATION" = "installation", + "INSTALLATION_CREATED" = "installation.created", + "INSTALLATION_DELETED" = "installation.deleted", + "INSTALLATION_NEW_PERMISSIONS_ACCEPTED" = "installation.new_permissions_accepted", + "INSTALLATION_SUSPEND" = "installation.suspend", + "INSTALLATION_UNSUSPEND" = "installation.unsuspend", + "INSTALLATION_REPOSITORIES" = "installation_repositories", + "INSTALLATION_REPOSITORIES_ADDED" = "installation_repositories.added", + "INSTALLATION_REPOSITORIES_REMOVED" = "installation_repositories.removed", + "ISSUE_COMMENT" = "issue_comment", + "ISSUE_COMMENT_CREATED" = "issue_comment.created", + "ISSUE_COMMENT_DELETED" = "issue_comment.deleted", + "ISSUE_COMMENT_EDITED" = "issue_comment.edited", + "ISSUES" = "issues", + "ISSUES_ASSIGNED" = "issues.assigned", + "ISSUES_CLOSED" = "issues.closed", + "ISSUES_DELETED" = "issues.deleted", + "ISSUES_DEMILESTONED" = "issues.demilestoned", + "ISSUES_EDITED" = "issues.edited", + "ISSUES_LABELED" = "issues.labeled", + "ISSUES_LOCKED" = "issues.locked", + "ISSUES_MILESTONED" = "issues.milestoned", + "ISSUES_OPENED" = "issues.opened", + "ISSUES_PINNED" = "issues.pinned", + "ISSUES_REOPENED" = "issues.reopened", + "ISSUES_TRANSFERRED" = "issues.transferred", + "ISSUES_UNASSIGNED" = "issues.unassigned", + "ISSUES_UNLABELED" = "issues.unlabeled", + "ISSUES_UNLOCKED" = "issues.unlocked", + "ISSUES_UNPINNED" = "issues.unpinned", + "LABEL" = "label", + "LABEL_CREATED" = "label.created", + "LABEL_DELETED" = "label.deleted", + "LABEL_EDITED" = "label.edited", + "MARKETPLACE_PURCHASE" = "marketplace_purchase", + "MARKETPLACE_PURCHASE_CANCELLED" = "marketplace_purchase.cancelled", + "MARKETPLACE_PURCHASE_CHANGED" = "marketplace_purchase.changed", + "MARKETPLACE_PURCHASE_PENDING_CHANGE" = "marketplace_purchase.pending_change", + "MARKETPLACE_PURCHASE_PENDING_CHANGE_CANCELLED" = "marketplace_purchase.pending_change_cancelled", + "MARKETPLACE_PURCHASE_PURCHASED" = "marketplace_purchase.purchased", + "MEMBER" = "member", + "MEMBER_ADDED" = "member.added", + "MEMBER_EDITED" = "member.edited", + "MEMBER_REMOVED" = "member.removed", + "MEMBERSHIP" = "membership", + "MEMBERSHIP_ADDED" = "membership.added", + "MEMBERSHIP_REMOVED" = "membership.removed", + "META" = "meta", + "META_DELETED" = "meta.deleted", + "MILESTONE" = "milestone", + "MILESTONE_CLOSED" = "milestone.closed", + "MILESTONE_CREATED" = "milestone.created", + "MILESTONE_DELETED" = "milestone.deleted", + "MILESTONE_EDITED" = "milestone.edited", + "MILESTONE_OPENED" = "milestone.opened", + "ORG_BLOCK" = "org_block", + "ORG_BLOCK_BLOCKED" = "org_block.blocked", + "ORG_BLOCK_UNBLOCKED" = "org_block.unblocked", + "ORGANIZATION" = "organization", + "ORGANIZATION_DELETED" = "organization.deleted", + "ORGANIZATION_MEMBER_ADDED" = "organization.member_added", + "ORGANIZATION_MEMBER_INVITED" = "organization.member_invited", + "ORGANIZATION_MEMBER_REMOVED" = "organization.member_removed", + "ORGANIZATION_RENAMED" = "organization.renamed", + "PACKAGE" = "package", + "PACKAGE_PUBLISHED" = "package.published", + "PACKAGE_UPDATED" = "package.updated", + "PAGE_BUILD" = "page_build", + "PING" = "ping", + "PROJECT" = "project", + "PROJECT_CLOSED" = "project.closed", + "PROJECT_CREATED" = "project.created", + "PROJECT_DELETED" = "project.deleted", + "PROJECT_EDITED" = "project.edited", + "PROJECT_REOPENED" = "project.reopened", + "PROJECT_CARD" = "project_card", + "PROJECT_CARD_CONVERTED" = "project_card.converted", + "PROJECT_CARD_CREATED" = "project_card.created", + "PROJECT_CARD_DELETED" = "project_card.deleted", + "PROJECT_CARD_EDITED" = "project_card.edited", + "PROJECT_CARD_MOVED" = "project_card.moved", + "PROJECT_COLUMN" = "project_column", + "PROJECT_COLUMN_CREATED" = "project_column.created", + "PROJECT_COLUMN_DELETED" = "project_column.deleted", + "PROJECT_COLUMN_EDITED" = "project_column.edited", + "PROJECT_COLUMN_MOVED" = "project_column.moved", + "PROJECTS_V2_ITEM" = "projects_v2_item", + "PROJECTS_V2_ITEM_ARCHIVED" = "projects_v2_item.archived", + "PROJECTS_V2_ITEM_CONVERTED" = "projects_v2_item.converted", + "PROJECTS_V2_ITEM_CREATED" = "projects_v2_item.created", + "PROJECTS_V2_ITEM_DELETED" = "projects_v2_item.deleted", + "PROJECTS_V2_ITEM_EDITED" = "projects_v2_item.edited", + "PROJECTS_V2_ITEM_REORDERED" = "projects_v2_item.reordered", + "PROJECTS_V2_ITEM_RESTORED" = "projects_v2_item.restored", + "PUBLIC" = "public", + "PULL_REQUEST" = "pull_request", + "PULL_REQUEST_ASSIGNED" = "pull_request.assigned", + "PULL_REQUEST_AUTO_MERGE_DISABLED" = "pull_request.auto_merge_disabled", + "PULL_REQUEST_AUTO_MERGE_ENABLED" = "pull_request.auto_merge_enabled", + "PULL_REQUEST_CLOSED" = "pull_request.closed", + "PULL_REQUEST_CONVERTED_TO_DRAFT" = "pull_request.converted_to_draft", + "PULL_REQUEST_EDITED" = "pull_request.edited", + "PULL_REQUEST_LABELED" = "pull_request.labeled", + "PULL_REQUEST_LOCKED" = "pull_request.locked", + "PULL_REQUEST_OPENED" = "pull_request.opened", + "PULL_REQUEST_READY_FOR_REVIEW" = "pull_request.ready_for_review", + "PULL_REQUEST_REOPENED" = "pull_request.reopened", + "PULL_REQUEST_REVIEW_REQUEST_REMOVED" = "pull_request.review_request_removed", + "PULL_REQUEST_REVIEW_REQUESTED" = "pull_request.review_requested", + "PULL_REQUEST_SYNCHRONIZE" = "pull_request.synchronize", + "PULL_REQUEST_UNASSIGNED" = "pull_request.unassigned", + "PULL_REQUEST_UNLABELED" = "pull_request.unlabeled", + "PULL_REQUEST_UNLOCKED" = "pull_request.unlocked", + "PULL_REQUEST_REVIEW" = "pull_request_review", + "PULL_REQUEST_REVIEW_DISMISSED" = "pull_request_review.dismissed", + "PULL_REQUEST_REVIEW_EDITED" = "pull_request_review.edited", + "PULL_REQUEST_REVIEW_SUBMITTED" = "pull_request_review.submitted", + "PULL_REQUEST_REVIEW_COMMENT" = "pull_request_review_comment", + "PULL_REQUEST_REVIEW_COMMENT_CREATED" = "pull_request_review_comment.created", + "PULL_REQUEST_REVIEW_COMMENT_DELETED" = "pull_request_review_comment.deleted", + "PULL_REQUEST_REVIEW_COMMENT_EDITED" = "pull_request_review_comment.edited", + "PULL_REQUEST_REVIEW_THREAD" = "pull_request_review_thread", + "PULL_REQUEST_REVIEW_THREAD_RESOLVED" = "pull_request_review_thread.resolved", + "PULL_REQUEST_REVIEW_THREAD_UNRESOLVED" = "pull_request_review_thread.unresolved", + "PUSH" = "push", + "RELEASE" = "release", + "RELEASE_CREATED" = "release.created", + "RELEASE_DELETED" = "release.deleted", + "RELEASE_EDITED" = "release.edited", + "RELEASE_PRERELEASED" = "release.prereleased", + "RELEASE_PUBLISHED" = "release.published", + "RELEASE_RELEASED" = "release.released", + "RELEASE_UNPUBLISHED" = "release.unpublished", + "REPOSITORY" = "repository", + "REPOSITORY_ARCHIVED" = "repository.archived", + "REPOSITORY_CREATED" = "repository.created", + "REPOSITORY_DELETED" = "repository.deleted", + "REPOSITORY_EDITED" = "repository.edited", + "REPOSITORY_PRIVATIZED" = "repository.privatized", + "REPOSITORY_PUBLICIZED" = "repository.publicized", + "REPOSITORY_RENAMED" = "repository.renamed", + "REPOSITORY_TRANSFERRED" = "repository.transferred", + "REPOSITORY_UNARCHIVED" = "repository.unarchived", + "REPOSITORY_DISPATCH" = "repository_dispatch", + "REPOSITORY_IMPORT" = "repository_import", + "REPOSITORY_VULNERABILITY_ALERT" = "repository_vulnerability_alert", + "REPOSITORY_VULNERABILITY_ALERT_CREATE" = "repository_vulnerability_alert.create", + "REPOSITORY_VULNERABILITY_ALERT_DISMISS" = "repository_vulnerability_alert.dismiss", + "REPOSITORY_VULNERABILITY_ALERT_REOPEN" = "repository_vulnerability_alert.reopen", + "REPOSITORY_VULNERABILITY_ALERT_RESOLVE" = "repository_vulnerability_alert.resolve", + "SECRET_SCANNING_ALERT" = "secret_scanning_alert", + "SECRET_SCANNING_ALERT_CREATED" = "secret_scanning_alert.created", + "SECRET_SCANNING_ALERT_REOPENED" = "secret_scanning_alert.reopened", + "SECRET_SCANNING_ALERT_RESOLVED" = "secret_scanning_alert.resolved", + "SECURITY_ADVISORY" = "security_advisory", + "SECURITY_ADVISORY_PERFORMED" = "security_advisory.performed", + "SECURITY_ADVISORY_PUBLISHED" = "security_advisory.published", + "SECURITY_ADVISORY_UPDATED" = "security_advisory.updated", + "SECURITY_ADVISORY_WITHDRAWN" = "security_advisory.withdrawn", + "SPONSORSHIP" = "sponsorship", + "SPONSORSHIP_CANCELLED" = "sponsorship.cancelled", + "SPONSORSHIP_CREATED" = "sponsorship.created", + "SPONSORSHIP_EDITED" = "sponsorship.edited", + "SPONSORSHIP_PENDING_CANCELLATION" = "sponsorship.pending_cancellation", + "SPONSORSHIP_PENDING_TIER_CHANGE" = "sponsorship.pending_tier_change", + "SPONSORSHIP_TIER_CHANGED" = "sponsorship.tier_changed", + "STAR" = "star", + "STAR_CREATED" = "star.created", + "STAR_DELETED" = "star.deleted", + "STATUS" = "status", + "TEAM" = "team", + "TEAM_ADDED_TO_REPOSITORY" = "team.added_to_repository", + "TEAM_CREATED" = "team.created", + "TEAM_DELETED" = "team.deleted", + "TEAM_EDITED" = "team.edited", + "TEAM_REMOVED_FROM_REPOSITORY" = "team.removed_from_repository", + "TEAM_ADD" = "team_add", + "WATCH" = "watch", + "WATCH_STARTED" = "watch.started", + "WORKFLOW_DISPATCH" = "workflow_dispatch", + "WORKFLOW_JOB" = "workflow_job", + "WORKFLOW_JOB_COMPLETED" = "workflow_job.completed", + "WORKFLOW_JOB_IN_PROGRESS" = "workflow_job.in_progress", + "WORKFLOW_JOB_QUEUED" = "workflow_job.queued", + "WORKFLOW_RUN" = "workflow_run", + "WORKFLOW_RUN_COMPLETED" = "workflow_run.completed", + "WORKFLOW_RUN_REQUESTED" = "workflow_run.requested", + "DEPENDABOT_ALERT" = "dependabot_alert", + "DEPLOYMENT_PROTECTION_RULE" = "deployment_protection_rule", + "INSTALLATION_TARGET" = "installation_target", + "MERGE_GROUP" = "merge_group", + "REGISTRY_PACKAGE" = "registry_package", + "SECRET_SCANNING_ALERT_LOCATION" = "secret_scanning_alert_location", +} diff --git a/src/github/utils/config.ts b/src/github/utils/config.ts new file mode 100644 index 0000000..4a27ac4 --- /dev/null +++ b/src/github/utils/config.ts @@ -0,0 +1,53 @@ +import { Value } from "@sinclair/typebox/value"; +import { GitHubContext } from "../github-context"; +import YAML from "yaml"; +import { Config, configSchema } from "../types/config"; + +const UBIQUIBOT_CONFIG_FULL_PATH = ".github/ubiquibot-config.yml"; + +export async function getConfig(context: GitHubContext): Promise { + const payload = context.payload; + if (!("repository" in payload) || !payload.repository) throw new Error("Repository is not defined"); + + const _repoConfig = parseYaml( + await download({ + context, + repository: payload.repository.name, + owner: payload.repository.owner.login, + }) + ); + + try { + return Value.Decode(configSchema, _repoConfig); + } catch (error) { + console.error("Error decoding config", error); + return null; + } +} + +async function download({ context, repository, owner }: { context: GitHubContext; repository: string; owner: string }): Promise { + if (!repository || !owner) throw new Error("Repo or owner is not defined"); + try { + const { data } = await context.octokit.rest.repos.getContent({ + owner, + repo: repository, + path: UBIQUIBOT_CONFIG_FULL_PATH, + mediaType: { format: "raw" }, + }); + return data as unknown as string; // this will be a string if media format is raw + } catch (err) { + return null; + } +} + +export function parseYaml(data: null | string) { + try { + if (data) { + const parsedData = YAML.parse(data); + return parsedData ?? null; + } + } catch (error) { + console.error("Error parsing YAML", error); + } + return null; +}