codeflash-internal/js/cf-api/github/github-utils.ts
Kevin Turcios d7a8b8f227
perf: fix CI build + lazy-load heavy libs + parallelize DB queries (#2601)
## Summary
- **Fix CI build failure**: Auth0Client crashes during Next.js
prerendering when env vars aren't set. Returns a no-op stub (`getSession
→ null`) when domain is missing — semantically correct for static
generation
- **Lazy-load markdown libs (~260kb)**: ReactMarkdown, remarkGfm, and
react-syntax-highlighter were eagerly imported in monaco-diff-viewer but
only rendered when user expands "Generated Tests". Extracted into a
dynamic component
- **Parallelize repo detail query**: `getRepositoryById` ran the
activity count sequentially after the repo lookup. Since `repoId` is
already available, all three queries now run in parallel

## Test plan
- [ ] CI `build` check passes (was failing since #2598)
- [ ] Trace page still renders generated tests correctly when expanded
- [ ] Repository detail page loads correctly with activity status
2026-04-13 11:03:05 -05:00

589 lines
17 KiB
TypeScript

import { Octokit, type App } from "octokit"
import * as Sentry from "@sentry/node"
import {
createAppInstallation,
createOrUpdateUser,
deleteOrganizationMemberApiKeys,
getAppInstallationByInstalltionId,
getUserById,
organizationMemberRepository,
organizationRepository,
prisma,
upsertRepository,
} from "@codeflash-ai/common"
import { sendSlackMessage } from "./slack_util.js"
import { isCodeflashEmployee } from "../utils/employee-utils.js"
// Channel for GitHub App installation alerts (falls back to default channel)
const GITHUB_ALERTS_CHANNEL = process.env.SLACK_CHANNEL_ID
// Dependencies interface for easier testing
export interface GithubUtilsDependencies {
console: {
error: typeof console.error
}
}
// Default dependencies
let dependencies: GithubUtilsDependencies = {
console: {
error: console.error,
},
}
// For testing - allow dependency injection
export function setGithubUtilsDependencies(deps: Partial<GithubUtilsDependencies>) {
dependencies = { ...dependencies, ...deps }
}
export function resetGithubUtilsDependencies() {
dependencies = {
console: {
error: console.error,
},
}
}
// Checks if the user is a collaborator on the repository
export async function isUserCollaborator(
installationOctokit: Octokit,
owner: string,
repo: string,
nickname: string,
): Promise<boolean> {
try {
const response = await installationOctokit.rest.repos.checkCollaborator({
owner,
repo,
username: nickname,
})
// If the request is successful, the user is a collaborator and we can continue
return response.status === 204
} catch (error: any) {
if (error.status === 404) {
return false // User is not a collaborator
}
// Handle other potential errors
throw error // Rethrow error for upstream handling
}
}
export async function getInstallationOctokitByOwner(
githubApp: App,
owner: string,
repo: string,
userId?: string,
): Promise<Octokit | Error> {
try {
const repoInstallation = await githubApp.octokit.rest.apps.getRepoInstallation({
owner,
repo,
})
const installationId = repoInstallation.data.id
return await githubApp.getInstallationOctokit(installationId)
} catch (error: any) {
dependencies.console.error(`Error getting installation Octokit for ${owner}/${repo}:`, error)
if (error.status === 404) {
// Send Slack alert for GitHub App installation failures
// This helps the team proactively reach out to affected customers
// Skip notification for Codeflash employees to reduce internal noise
if (!userId || !isCodeflashEmployee(userId)) {
sendSlackMessage(
{
text: `GitHub App Installation Issue`,
blocks: [
{
type: "header",
text: {
type: "plain_text",
text: "GitHub App Installation Issue",
emoji: true,
},
},
{
type: "section",
fields: [
{
type: "mrkdwn",
text: `*Repository:*\n${owner}/${repo}`,
},
{
type: "mrkdwn",
text: `*Error:*\nApp not installed (404)`,
},
],
},
{
type: "section",
text: {
type: "mrkdwn",
text: `This may indicate the customer needs to accept updated GitHub App permissions or reinstall the app.\n\n<https://github.com/${owner}|View Organization on GitHub>`,
},
},
{
type: "context",
elements: [
{
type: "mrkdwn",
text: `Timestamp: ${new Date().toISOString()}`,
},
],
},
],
},
GITHUB_ALERTS_CHANNEL,
).catch(slackError => {
// Don't let Slack errors affect the main flow
console.error("Failed to send Slack alert for GitHub App installation issue:", slackError)
})
}
return Error(`GitHub App is not installed on the repository ${owner}/${repo}`)
} else {
return Error(`Error checking GitHub App installation status for repo ${owner}/${repo}`)
}
}
}
function isLabelAlreadyExistsError(error: any): boolean {
return (
error?.status === 422 &&
Array.isArray(error?.response?.data?.errors) &&
error.response.data.errors.some(
(e: any) => e.resource === "Label" && e.code === "already_exists" && e.field === "name",
)
)
}
// Ensures that a label exists on a repository, creating it if it doesn't
export async function ensureLabelExists(
installationOctokit: Octokit,
owner: string,
repo: string,
labelName: string,
color: string,
description: string,
): Promise<void> {
try {
await installationOctokit.rest.issues.getLabel({
owner,
repo,
name: labelName,
})
} catch (error: any) {
if (error.status === 404) {
// Label does not exist, create it
try {
await installationOctokit.rest.issues.createLabel({
owner,
repo,
name: labelName,
color,
description,
})
} catch (createError: any) {
// Ignore "already_exists" error - another concurrent request may have created it
if (!isLabelAlreadyExistsError(createError)) {
Sentry.captureException(createError, {
extra: { owner, repo, labelName, context: "ensureLabelExists - createLabel failed" },
})
}
}
} else {
// Log to Sentry and prevent throwing the error
Sentry.captureException(error, {
extra: { owner, repo, labelName, context: "ensureLabelExists - getLabel failed" },
})
}
}
}
// Adds a label to a pull request, creating the label if it doesn't exist
export async function addLabelToPullRequest(
installationOctokit: Octokit,
owner: string,
repo: string,
pullNumber: number,
labelName: string = "⚡️ codeflash",
labelColor: string = "FFC043",
description: string = "Optimization PR opened by Codeflash AI",
): Promise<void> {
await ensureLabelExists(installationOctokit, owner, repo, labelName, labelColor, description)
await installationOctokit.rest.issues.addLabels({
owner,
repo,
issue_number: pullNumber,
labels: [labelName],
})
}
/**
* Fetches the user's role in an organization.
* Expected values: "admin" (owner), "member" (normal member).
*/
async function fetchOrgRole(octokit: Octokit, owner: string, username: string): Promise<string> {
try {
const { data: membership } = await octokit.rest.orgs.getMembershipForUser({
org: owner,
username,
})
return membership.role || ""
} catch (error) {
console.error(`Error fetching org role for ${username} in ${owner}:`, error)
Sentry.captureException(error)
return ""
}
}
/**
* Fetches the user's permission level in a repository.
* Expected values: "admin", "maintain", "write", "triage", "read".
*/
async function fetchRepoRole(
octokit: Octokit,
owner: string,
repo: string,
username: string,
): Promise<string> {
try {
const { data: permissions } = await octokit.rest.repos.getCollaboratorPermissionLevel({
owner,
repo,
username,
})
console.log(`Repo role for ${username} in ${owner}/${repo}: ${permissions.permission}`)
return permissions.permission || ""
} catch (error) {
console.error(`Error fetching repo role for ${username} in ${owner}/${repo}:`, error)
Sentry.captureException(error)
return ""
}
}
/**
* Returns the user's role in an org or a repo, depending on what's available.
* If it fails, logs the error to Sentry and returns an empty string.
*/
export async function getUserRole({
octokit,
owner,
repo,
username,
isOrg,
}: {
octokit: Octokit
owner: string
repo: string
username: string
isOrg: boolean
}): Promise<string> {
if (isOrg) {
// Fetch org membership role
// Expected values: "admin" (org owner) or "member" (regular member)
return await fetchOrgRole(octokit, owner, username)
} else {
// Fetch repo permission
// Expected values: "admin", "maintain", "write", "triage", "read"
return await fetchRepoRole(octokit, owner, repo, username)
}
}
async function getInstallations(app: App) {
const installations: any[] = []
let page = 1
console.log("fetching installations...")
while (true) {
try {
const response = await app.octokit.rest.apps.listInstallations({
per_page: 90, // max is 90
page,
})
if (!response.data.length) break
installations.push(...response.data)
page++
} catch (error) {
console.error("Error fetching installations:", error)
Sentry.captureException(error)
break
}
}
console.log("Done")
return installations
}
/**
* Checks whether a branch exists in a GitHub repository.
*
* @param {object} params - Parameters for branch existence check.
* @param {string} params.owner - The repository owner.
* @param {string} params.repo - The repository name.
* @param {string} params.branch - The branch name to check.
* @param {Octokit} params.installationOctokit - The authenticated installation Octokit client for the repo.
* @returns {Promise<boolean>} - Returns true if the branch exists, false if not.
*
* @example
* const exists = await isBranchExists({
* owner: "octocat",
* repo: "Hello-World",
* branch: "feature-branch",
* installationOctokit,
* });
* console.log("Branch exists:", exists);
*/
export async function isBranchExists({
owner,
repo,
branch,
installationOctokit,
}: {
owner: string
repo: string
branch: string
installationOctokit: Octokit
}): Promise<boolean> {
try {
await installationOctokit.rest.git.getRef({
owner,
repo,
ref: `heads/${branch}`,
})
return true
} catch (error: any) {
if (error.status === 404) {
return false
}
Sentry.captureException(error)
dependencies.console.error(
`Error checking if branch ${branch} exists in ${owner}/${repo}:`,
error,
)
return false
}
}
async function filterInstalltionAccountByOrg(installations: any, orgNames?: string[]) {
let orgInstallations: typeof installations = []
for (const installation of installations) {
const login = installation.account?.login
if (installation.account?.type === "Organization" && login) {
orgInstallations.push(installation)
}
}
if (orgNames && orgNames.length) {
orgInstallations = orgInstallations.filter(install => orgNames.includes(install.account!.login))
}
return orgInstallations
}
async function getReposForInstallation(installationOctokit: Octokit): Promise<any[]> {
const repos: any[] = []
let page = 1
while (true) {
try {
const reposResponse = await installationOctokit.rest.apps.listReposAccessibleToInstallation({
per_page: 90,
page,
})
if (!reposResponse.data.repositories.length) break
repos.push(...reposResponse.data.repositories)
page++
} catch (error) {
console.error("Error fetching repos for installation:", error)
Sentry.captureException(error)
break
}
}
return repos
}
async function getMembersWithRolesForOrg(
installationOctokit: Octokit,
orgLogin: string,
): Promise<Array<{ id: number; username: string; role: string }>> {
const members: Array<{ id: number; username: string; role: string }> = []
const memberData: Array<{ id: number; login: string }> = []
let page = 1
// ---- Fetch members (paginated) ----
while (true) {
try {
const membersResponse = await installationOctokit.rest.orgs.listMembers({
org: orgLogin,
per_page: 90,
page,
})
if (!membersResponse.data.length) break
memberData.push(
...membersResponse.data.map(member => ({
id: member.id,
login: member.login,
})),
)
page++
} catch (error) {
console.error(`Error fetching members for org ${orgLogin}:`, error)
Sentry.captureException(error)
break
}
}
// ---- Fetch roles for each member ----
for (const member of memberData) {
try {
const role = await fetchOrgRole(installationOctokit, orgLogin, member.login)
members.push({ id: member.id, username: member.login, role })
} catch (error) {
console.error(`Error fetching role for member ${member.login} in org ${orgLogin}:`, error)
Sentry.captureException(error)
}
}
return members
}
export async function syncOrgsWithMembers(app: App, orgNames?: string[]) {
Sentry.captureMessage("syncOrgsWithMembers started")
const installations = await getInstallations(app)
const orgInstallations = await filterInstalltionAccountByOrg(installations, orgNames)
for (const installation of orgInstallations) {
try {
const login = installation.account!.login
let repos: any[] = []
let members: Array<{ id: number; username: string; role: string }> = []
console.log(`fetch repos for ${login}`)
const installationOctokit = await app.getInstallationOctokit(installation.id)
const accountLogin = installation.account!.login
const accountType = installation.account!.type
// Check if the installation exists, if not, create it
const installationExists = await getAppInstallationByInstalltionId(installation.id)
if (!installationExists) {
await createAppInstallation({
installation_id: installation.id,
account_id: installation.account!.id,
account_login: accountLogin,
account_type: accountType,
})
console.log(`Installation created for ID: ${installation.id}`)
}
// --- Fetch all repos with pagination ---
repos = await getReposForInstallation(installationOctokit)
console.log("Done... ")
console.log(`fetch members for ${login}`)
// --- Fetch all members with roles ---
members = await getMembersWithRolesForOrg(installationOctokit, login)
console.log("Done... ")
// Get or create organization
let organization = await organizationRepository.findByGithubOrgId(
String(installation.account!.id),
)
organization ||= await organizationRepository.create({
github_org_id: String(installation.account!.id),
name: login,
added_by: "Codeflash",
})
// Fetch existing members in organization from DB
const existingMembersInDb = await prisma.organization_members.findMany({
where: { organization_id: organization.id },
})
// Current member IDs from GitHub
const currentMemberIds = members.map(m => `github|${m.id}`)
// Remove members who no longer exist in the org
for (const existingMember of existingMembersInDb) {
if (!currentMemberIds.includes(String(existingMember.user_id))) {
await organizationMemberRepository.removeMember(
String(organization.id),
String(existingMember.user_id),
)
await deleteOrganizationMemberApiKeys(
String(existingMember.user_id),
String(organization.id),
)
}
}
// Process each member: create or update user and add/update organization membership
for (const member of members) {
const userId = `github|${member.id}`
const existingUser = await getUserById(userId)
if (!existingUser) {
await createOrUpdateUser(userId, member.username, null, null)
}
try {
// Check if member already exists in organization_members
const existingMember = await prisma.organization_members.findUnique({
where: {
organization_id_user_id: {
organization_id: organization.id,
user_id: userId,
},
},
})
if (!existingMember) {
await organizationMemberRepository.addMember({
organizationId: organization.id,
userId,
role: member.role,
})
} else if (existingMember.role !== member.role) {
// Update role if changed
await organizationMemberRepository.updateRole(organization.id, userId, member.role)
}
} catch (error) {
// If error is due to unique constraint, skip
if (error instanceof Error && error.message.includes("Unique constraint failed")) {
// skip
} else {
throw error
}
}
}
// Process each repo
for (const repo of repos) {
// Upsert repository
await upsertRepository({
github_repo_id: String(repo.id),
installation_id: installation.id,
name: repo.name,
full_name: repo.full_name,
is_private: repo.private,
organization_id: organization.id,
})
}
} catch (error) {
dependencies.console.error(
`Error fetching data for org ${installation.account!.login}:`,
error,
)
Sentry.captureException(error)
continue
}
}
Sentry.captureMessage("syncOrgsWithMembers completed")
}