292 lines
11 KiB
TypeScript
292 lines
11 KiB
TypeScript
import { App, createNodeMiddleware } from "octokit"
|
|
import fs from "fs"
|
|
// import {getGithubAppPrivateKey, getGithubAppWebhookSecret} from "../common/azure-keyvault";
|
|
// TODO the above import doesn't work, we have to do this vvv
|
|
import azureKeyvault from "@codeflash-ai/common/azure-keyvault"
|
|
import { posthog } from "../analytics"
|
|
|
|
const { getGithubAppPrivateKey, getGithubAppWebhookSecret } = azureKeyvault
|
|
|
|
const APP_ID: string = process.env.GH_APP_ID ?? "" // GitHub App ID
|
|
const APP_USER_ID: number = parseInt(process.env.GH_APP_USER_ID ?? "0") // GitHub App User ID
|
|
|
|
const PRIVATE_KEY: string =
|
|
process.env.NODE_ENV === "production"
|
|
? await getGithubAppPrivateKey()
|
|
: fs.readFileSync("github/Codeflash AI Dev GitHub App Private Key.pem", "utf8")
|
|
const WEBHOOK_SECRET: string =
|
|
process.env.NODE_ENV === "production"
|
|
? await getGithubAppWebhookSecret()
|
|
: process.env.GH_APP_WEBHOOK_SECRET
|
|
|
|
export const githubApp = new App({
|
|
appId: APP_ID,
|
|
privateKey: PRIVATE_KEY,
|
|
webhooks: {
|
|
secret: WEBHOOK_SECRET,
|
|
},
|
|
oauth: {
|
|
// OAuth details are currently unused by the app
|
|
clientId: "", // process.env.GH_APP_CLIENT_ID ?? "",
|
|
clientSecret: "", // process.env.GH_APP_CLIENT_SECRET ?? "",
|
|
},
|
|
})
|
|
console.log(`Github App Initialized`)
|
|
|
|
const { data } = await githubApp.octokit.request("/app")
|
|
// Read more about custom logging: https://github.com/octokit/core.js#logging
|
|
githubApp.octokit.log.debug(`Authenticated as '${data.name}'`)
|
|
|
|
githubApp.webhooks.onAny(async ({ id, name, payload }) => {
|
|
console.log(`Github App: Received webhook event ${name} (${id})`)
|
|
console.log(`Payload: ${JSON.stringify(payload)}`)
|
|
posthog.capture({
|
|
distinctId: `github|${payload.sender.id}`,
|
|
event: `cfapi-github-webhook-received`,
|
|
properties: {
|
|
event: name,
|
|
id,
|
|
},
|
|
})
|
|
})
|
|
|
|
console.log(`Console: Github App Authenticated as '${data.name}'`)
|
|
|
|
githubApp.webhooks.on("installation", async ({ octokit, payload }) => {
|
|
console.log(`Received a new installation event: ${JSON.stringify(payload)}`)
|
|
// Create an installation access token
|
|
const installationAccessToken = await octokit.rest.apps.createInstallationAccessToken({
|
|
installation_id: payload.installation.id,
|
|
})
|
|
console.log(`Installation access token: ${installationAccessToken.data.token}`)
|
|
})
|
|
|
|
githubApp.webhooks.on("pull_request.opened", async ({ octokit, payload }) => {
|
|
console.log(`Received a pull request event: ${JSON.stringify(payload)}`)
|
|
try {
|
|
console.log(`Successfully processed this!!!`)
|
|
} catch (error: any) {
|
|
if (error.response !== undefined) {
|
|
console.error(
|
|
`Error! Status: ${error.response.status}. Message: ${error.response.data.message}`,
|
|
)
|
|
} else {
|
|
console.error(error)
|
|
}
|
|
}
|
|
})
|
|
githubApp.webhooks.on("pull_request.edited", async ({ octokit, payload }) => {
|
|
console.log(`Received a pull request event: ${JSON.stringify(payload)}`)
|
|
try {
|
|
console.log(`Successfully processed this!!!`)
|
|
} catch (error: any) {
|
|
if (error.response !== undefined) {
|
|
console.error(
|
|
`Error! Status: ${error.response.status}. Message: ${error.response.data.message}`,
|
|
)
|
|
} else {
|
|
console.error(error)
|
|
}
|
|
}
|
|
})
|
|
|
|
githubApp.webhooks.on("pull_request.closed", async ({ octokit, payload }) => {
|
|
if (payload.pull_request) {
|
|
console.log(
|
|
`Received a pull request closed event. PR #${payload.pull_request.number} ` +
|
|
`by ${payload.pull_request.user.login} was closed.`,
|
|
)
|
|
|
|
// Check if the PR was merged and is a PR created by Codeflash
|
|
if (payload.pull_request.merged && payload.pull_request.user.id === APP_USER_ID) {
|
|
// Extract the original PR number from the branch name
|
|
const dependentBranchNamePattern = /codeflash\/optimize-pr(\d+)-\d{4}-\d{2}-\d{2}T.+$/
|
|
const standaloneBranchNamePattern = /codeflash\/optimize-(.+)-\d{4}-\d{2}-\d{2}T.+$/
|
|
const dependentPrMatch = dependentBranchNamePattern.exec(payload.pull_request.head.ref)
|
|
const standalonePrMatch = standaloneBranchNamePattern.exec(payload.pull_request.head.ref)
|
|
if (dependentPrMatch != null) {
|
|
const originalPrNumber = parseInt(dependentPrMatch[1])
|
|
// Comment on the original PR
|
|
await octokit.rest.issues.createComment({
|
|
owner: payload.repository.owner.login,
|
|
repo: payload.repository.name,
|
|
issue_number: originalPrNumber,
|
|
body: `${payload.pull_request.merged_by.login} accepted my optimizations from PR #${payload.pull_request.number}. This PR is now faster! 🚀`,
|
|
})
|
|
posthog.capture({
|
|
distinctId: `github|${payload.sender.id}`, // this is the user who merged the PR
|
|
event: `cfapi-github-dependent-pr-merged`,
|
|
properties: {
|
|
originalPrNumber,
|
|
dependentPrNumber: payload.pull_request.number,
|
|
mergedBy: payload.pull_request.merged_by.login,
|
|
},
|
|
})
|
|
console.log(
|
|
`Commented on original PR #${originalPrNumber} and logged the event to Posthog.`,
|
|
)
|
|
} else if (standalonePrMatch != null) {
|
|
posthog.capture({
|
|
distinctId: `github|${payload.sender.id}`,
|
|
event: `cfapi-github-standalone-pr-merged`,
|
|
properties: {
|
|
functionName: standalonePrMatch[1],
|
|
prNumber: payload.pull_request.number,
|
|
mergedBy: payload.pull_request.merged_by.login,
|
|
},
|
|
})
|
|
console.log(`Logged standalone PR #${payload.pull_request.number} merge event to Posthog.`)
|
|
}
|
|
}
|
|
|
|
// Close any open optimization PRs targeting the branch of the closed PR
|
|
// Ensure we only close PRs that are targeting the branch of the PR that was just closed
|
|
const closedPrBranch = payload.pull_request.head.ref
|
|
// Logic to close any open optimization PRs targeting this branch
|
|
console.log(`Closing optimization PRs targeting branch ${closedPrBranch}...`)
|
|
if (payload.installation === undefined) {
|
|
console.error(
|
|
`Error! Installation ID is missing from payload. Cannot close PRs for this installation!`,
|
|
)
|
|
return
|
|
}
|
|
try {
|
|
const installationOctokit = await githubApp.getInstallationOctokit(payload.installation.id)
|
|
const openPrs = await installationOctokit.rest.pulls.list({
|
|
owner: payload.repository.owner.login,
|
|
repo: payload.repository.name,
|
|
state: "open",
|
|
base: closedPrBranch,
|
|
})
|
|
|
|
for (const pr of openPrs.data) {
|
|
// Check if the PR is opened by the Codeflash GitHub App and targets the same base branch as the closed PR
|
|
if (
|
|
pr.user?.type === "Bot" &&
|
|
pr.user?.id === APP_USER_ID &&
|
|
pr.base.ref === closedPrBranch
|
|
) {
|
|
await installationOctokit.rest.pulls.update({
|
|
owner: payload.repository.owner.login,
|
|
repo: payload.repository.name,
|
|
pull_number: pr.number,
|
|
state: "closed",
|
|
})
|
|
console.log(
|
|
`Closed optimization PR #${pr.number} targeting branch '${closedPrBranch}' ` +
|
|
`because original PR #${payload.pull_request.number} by ${payload.pull_request.user.login} was closed.`,
|
|
)
|
|
console.log(`Posting pull request comment...`)
|
|
await octokit.rest.issues.createComment({
|
|
owner: payload.repository.owner.login,
|
|
repo: payload.repository.name,
|
|
issue_number: pr.number,
|
|
body:
|
|
`>This PR has been automatically closed because the original PR #${payload.pull_request.number} ` +
|
|
`by ${payload.pull_request.user.login} was closed.`,
|
|
})
|
|
console.log(`Deleting the branch associated with the closed PR...`)
|
|
await installationOctokit.rest.git.deleteRef({
|
|
owner: payload.repository.owner.login,
|
|
repo: payload.repository.name,
|
|
ref: `heads/${pr.head.ref}`,
|
|
})
|
|
console.log(
|
|
`Branch '${pr.head.ref}' associated with the closed PR #${pr.number} has been deleted.`,
|
|
)
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.error(`Failed to close optimization PRs targeting branch ${closedPrBranch}: ${error}`)
|
|
}
|
|
}
|
|
})
|
|
|
|
githubApp.webhooks.on("installation.created", async ({ octokit, payload }) => {
|
|
console.log(`Received a installation.created event: ${JSON.stringify(payload)}`)
|
|
try {
|
|
console.log(`Successfully processed this!!!`)
|
|
} catch (error: any) {
|
|
if (error.response !== undefined) {
|
|
console.error(
|
|
`Error! Status: ${error.response.status}. Message: ${error.response.data.message}`,
|
|
)
|
|
} else {
|
|
console.error(error)
|
|
}
|
|
}
|
|
})
|
|
|
|
githubApp.webhooks.on("installation_repositories.added", async ({ octokit, payload }) => {
|
|
console.log(`Received a installation_repositories.added event: ${JSON.stringify(payload)}`)
|
|
try {
|
|
console.log(`Successfully processed this!!!`)
|
|
} catch (error: any) {
|
|
if (error.response !== undefined) {
|
|
console.error(
|
|
`Error! Status: ${error.response.status}. Message: ${error.response.data.message}`,
|
|
)
|
|
} else {
|
|
console.error(error)
|
|
}
|
|
}
|
|
})
|
|
|
|
githubApp.webhooks.on("pull_request.synchronize", async ({ octokit, payload }) => {
|
|
if (payload.pull_request) {
|
|
console.log(
|
|
`Received a pull request synchronize event. PR #${payload.pull_request.number} ` +
|
|
`by ${payload.pull_request.user.login} was updated with new commits.`,
|
|
)
|
|
// Retrieve the list of commits for the pull request
|
|
const commits = await octokit.rest.pulls.listCommits({
|
|
owner: payload.repository.owner.login,
|
|
repo: payload.repository.name,
|
|
pull_number: payload.pull_request.number,
|
|
})
|
|
|
|
// Check the latest commit for the co-authored-by line
|
|
const latestCommit = commits.data[commits.data.length - 1]
|
|
if (latestCommit.commit.message.includes("Co-authored-by: codeflash-ai[bot]")) {
|
|
// Log the event to Posthog
|
|
posthog.capture({
|
|
distinctId: `github|${payload.sender.id}`,
|
|
event: `cfapi-github-commit-coauthored-by-codeflash`,
|
|
properties: {
|
|
prNumber: payload.pull_request.number,
|
|
commitId: latestCommit.sha,
|
|
repository: payload.repository.full_name,
|
|
},
|
|
})
|
|
console.log(`Logged co-authored commit to Posthog: ${latestCommit.sha}`)
|
|
|
|
// Comment on the PR
|
|
await octokit.rest.issues.createComment({
|
|
owner: payload.repository.owner.login,
|
|
repo: payload.repository.name,
|
|
issue_number: payload.pull_request.number,
|
|
body: `${latestCommit.commit.author.name} accepted my code suggestion above. This PR is now faster! 🚀`,
|
|
})
|
|
console.log(
|
|
`Commented on PR #${payload.pull_request.number} about the accepted review comment.`,
|
|
)
|
|
}
|
|
}
|
|
})
|
|
|
|
// Optional: Handle errors
|
|
githubApp.webhooks.onError((error) => {
|
|
console.log(`Error occured in Github App: ${error}`)
|
|
if (error.name === "AggregateError") {
|
|
// Log Secret verification errors
|
|
console.log(`Possible webhook secret verification error: ${JSON.stringify(error.event)}`)
|
|
} else {
|
|
console.log(error)
|
|
}
|
|
})
|
|
|
|
export const ghAppPathPrefix: string = "/cfapi/github"
|
|
|
|
export const ghAppMiddleware = createNodeMiddleware(githubApp, {
|
|
pathPrefix: ghAppPathPrefix,
|
|
})
|