codeflash-internal/js/cf-api/endpoints/close-pr.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

192 lines
5.2 KiB
TypeScript

import { Request, Response } from "express"
import { AuthorizedUserReq } from "../types.js"
import { userNickname } from "../auth0-mgmt.js"
import { getInstallationOctokitByOwner, isUserCollaborator } from "../github/github-utils.js"
import { githubApp } from "../github/github-app.js"
import { logger } from "../utils/logger.js"
import {
missingRequiredFields,
validationFailure,
unauthorized,
githubInstallationError,
githubNotCollaborator,
githubPrNotFound,
forbidden,
internalServerError,
} from "../exceptions/index.js"
// Dependencies interface for easier testing
export interface ClosePrDependencies {
userNickname: typeof userNickname
getInstallationOctokitByOwner: typeof getInstallationOctokitByOwner
isUserCollaborator: typeof isUserCollaborator
githubApp: typeof githubApp
}
// Default dependencies
let dependencies: ClosePrDependencies = {
userNickname,
getInstallationOctokitByOwner,
isUserCollaborator,
githubApp,
}
// For testing - allow dependency injection
export function setClosePrDependencies(deps: Partial<ClosePrDependencies>) {
dependencies = { ...dependencies, ...deps }
}
export function resetClosePrDependencies() {
dependencies = {
userNickname,
getInstallationOctokitByOwner,
isUserCollaborator,
githubApp,
}
}
/**
* Close a PR that was created by Codeflash
*
* This endpoint allows closing PRs that were raised by mistake on client repositories.
* It verifies that:
* 1. The user is authenticated and is a collaborator on the repo
* 2. The PR was created by the Codeflash bot (safety check)
* 3. The PR exists and is open
*/
export async function closePr(req: Request, res: Response): Promise<void> {
const { owner, repo, pr_number, comment } = req.body
// Validate required fields
if (!owner || !repo || !pr_number) {
throw missingRequiredFields("owner, repo, pr_number")
}
const ownerStr = String(owner).trim()
const repoStr = String(repo).trim()
const prNumber = Number(pr_number)
if (ownerStr === "" || repoStr === "") {
throw validationFailure("owner and repo cannot be empty")
}
if (isNaN(prNumber) || prNumber <= 0) {
throw validationFailure("pr_number must be a positive integer")
}
try {
// Verify user is authenticated
const nickname = await dependencies.userNickname((req as AuthorizedUserReq).userId)
if (nickname == null) {
throw unauthorized("")
}
// Get installation octokit
const installationOctokit = await dependencies.getInstallationOctokitByOwner(
dependencies.githubApp,
ownerStr,
repoStr,
)
if (installationOctokit instanceof Error) {
throw githubInstallationError(installationOctokit.message)
}
//Verify user is a collaborator
const isCollaborator = await dependencies.isUserCollaborator(
installationOctokit,
ownerStr,
repoStr,
nickname,
)
if (!isCollaborator) {
throw githubNotCollaborator(`${ownerStr}/${repoStr}`)
}
// Get the PR to verify it exists and was created by Codeflash
let pr
try {
const prResponse = await installationOctokit.rest.pulls.get({
owner: ownerStr,
repo: repoStr,
pull_number: prNumber,
})
pr = prResponse.data
} catch (error: any) {
if (error.status === 404) {
throw githubPrNotFound(`${ownerStr}/${repoStr}#${prNumber}`)
}
throw error
}
// Safety check: Only allow closing PRs created by the Codeflash bot
const isCodeflashBot = pr.user?.type === "Bot" && pr.user?.login?.includes("codeflash")
if (!isCodeflashBot) {
throw forbidden(
`Cannot close PR #${prNumber} - it was not created by Codeflash. ` +
`PR was created by ${pr.user?.login} (${pr.user?.type})`,
)
}
// Check if PR is already closed
if (pr.state === "closed") {
logger.info("PR is already closed", req, {
repo: `${ownerStr}/${repoStr}`,
prNumber,
})
res.json({
success: true,
message: `PR #${prNumber} is already closed`,
pr_url: pr.html_url,
state: pr.state,
})
return
}
// Add a comment if provided
const closeComment =
comment || `This PR has been manually closed by ${nickname} via Codeflash admin API.`
await installationOctokit.rest.issues.createComment({
owner: ownerStr,
repo: repoStr,
issue_number: prNumber,
body: closeComment,
})
// Close the PR
await installationOctokit.rest.pulls.update({
owner: ownerStr,
repo: repoStr,
pull_number: prNumber,
state: "closed",
})
logger.info("Successfully closed PR", req, {
repo: `${ownerStr}/${repoStr}`,
prNumber,
closedBy: nickname,
})
res.json({
success: true,
message: `Successfully closed PR #${prNumber}`,
pr_url: pr.html_url,
closed_by: nickname,
})
} catch (error: any) {
// Re-throw AppExceptions
if (error && typeof error === "object" && "getHttpStatus" in error) {
throw error
}
logger.errorWithSentry(
"Error closing PR",
req,
{ repo: `${ownerStr}/${repoStr}`, prNumber },
error,
)
throw internalServerError("Error closing PR")
}
}