π File detail
utils/ghPrStatus.ts
π§© .tsπ 107 linesπΎ 2,823 bytesπ text
β Back to All Filesπ― Use case
This file lives under βutils/β, which covers cross-cutting helpers (shell, tempfiles, settings, messages, process input, β¦). On the API surface it exposes PrReviewState, PrStatus, deriveReviewState, and fetchPrStatus β mainly functions, hooks, or classes. It composes internal code from execFileNoThrow, git, and slowOperations (relative imports).
Generated from folder role, exports, dependency roots, and inline comments β not hand-reviewed for every path.
π§ Inline summary
import { execFileNoThrow } from './execFileNoThrow.js' import { getBranch, getDefaultBranch, getIsGit } from './git.js' import { jsonParse } from './slowOperations.js' export type PrReviewState =
π€ Exports (heuristic)
PrReviewStatePrStatusderiveReviewStatefetchPrStatus
π₯οΈ Source preview
import { execFileNoThrow } from './execFileNoThrow.js'
import { getBranch, getDefaultBranch, getIsGit } from './git.js'
import { jsonParse } from './slowOperations.js'
export type PrReviewState =
| 'approved'
| 'pending'
| 'changes_requested'
| 'draft'
| 'merged'
| 'closed'
export type PrStatus = {
number: number
url: string
reviewState: PrReviewState
}
const GH_TIMEOUT_MS = 5000
/**
* Derive review state from GitHub API values.
* Draft PRs always show as 'draft' regardless of reviewDecision.
* reviewDecision can be: APPROVED, CHANGES_REQUESTED, REVIEW_REQUIRED, or empty string.
*/
export function deriveReviewState(
isDraft: boolean,
reviewDecision: string,
): PrReviewState {
if (isDraft) return 'draft'
switch (reviewDecision) {
case 'APPROVED':
return 'approved'
case 'CHANGES_REQUESTED':
return 'changes_requested'
default:
return 'pending'
}
}
/**
* Fetch PR status for the current branch using `gh pr view`.
* Returns null on any failure (gh not installed, no PR, not in git repo, etc).
* Also returns null if the PR's head branch is the default branch (e.g., main/master).
*/
export async function fetchPrStatus(): Promise<PrStatus | null> {
const isGit = await getIsGit()
if (!isGit) return null
// Skip on the default branch β `gh pr view` returns the most recently
// merged PR there, which is misleading.
const [branch, defaultBranch] = await Promise.all([
getBranch(),
getDefaultBranch(),
])
if (branch === defaultBranch) return null
const { stdout, code } = await execFileNoThrow(
'gh',
[
'pr',
'view',
'--json',
'number,url,reviewDecision,isDraft,headRefName,state',
],
{ timeout: GH_TIMEOUT_MS, preserveOutputOnError: false },
)
if (code !== 0 || !stdout.trim()) return null
try {
const data = jsonParse(stdout) as {
number: number
url: string
reviewDecision: string
isDraft: boolean
headRefName: string
state: string
}
// Don't show PR status for PRs from the default branch (e.g., main, master)
// This can happen when someone opens a PR from main to another branch
if (
data.headRefName === defaultBranch ||
data.headRefName === 'main' ||
data.headRefName === 'master'
) {
return null
}
// Don't show PR status for merged or closed PRs β `gh pr view` returns
// the most recently associated PR for a branch, which may be merged/closed.
// The status line should only display open PRs.
if (data.state === 'MERGED' || data.state === 'CLOSED') {
return null
}
return {
number: data.number,
url: data.url,
reviewState: deriveReviewState(data.isDraft, data.reviewDecision),
}
} catch {
return null
}
}