codeflash-internal/js/cf-api/github/github-utils.ts
HeshamHM28 6f5c2d7ad8
Implement Tests for CF-API Flow (#1634)
Co-authored-by: Sarthak Agarwal <sarthak.saga@gmail.com>
2025-06-25 03:36:26 +05:30

199 lines
5.3 KiB
TypeScript

import { type App, type Octokit } from "octokit"
import * as Sentry from "@sentry/node"
// 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,
): 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) {
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}`)
}
}
}
// 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
await installationOctokit.rest.issues.createLabel({
owner,
repo,
name: labelName,
color,
description,
})
} else {
// An error occurred that wasn't a 404, rethrow it
throw error
}
}
}
// 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,
})
console.log(`Org role for ${username} in ${owner}: ${membership.role}`)
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)
}
}