( runtime: PRRuntime, ref: GhPRRef, )
| 152 | } |
| 153 | |
| 154 | export async function fetchGhPR( |
| 155 | runtime: PRRuntime, |
| 156 | ref: GhPRRef, |
| 157 | ): Promise<{ metadata: PRMetadata; rawPatch: string; patchIncomplete?: boolean }> { |
| 158 | const repo = repoFlag(ref); |
| 159 | |
| 160 | // Fetch diff, metadata, and repository defaults in parallel. |
| 161 | const [diffResult, viewResult, repoResult] = await Promise.all([ |
| 162 | runtime.runCommand("gh", [ |
| 163 | "pr", "diff", String(ref.number), |
| 164 | "--repo", repo, |
| 165 | ]), |
| 166 | runtime.runCommand("gh", [ |
| 167 | "pr", "view", String(ref.number), |
| 168 | "--repo", repo, |
| 169 | "--json", "id,title,author,baseRefName,headRefName,baseRefOid,headRefOid,url,changedFiles", |
| 170 | ]), |
| 171 | runtime.runCommand("gh", [ |
| 172 | "repo", "view", repo, |
| 173 | "--json", "defaultBranchRef", |
| 174 | "--jq", ".defaultBranchRef.name", |
| 175 | ]), |
| 176 | ]); |
| 177 | |
| 178 | if (viewResult.exitCode !== 0) { |
| 179 | throw new Error( |
| 180 | `Failed to fetch PR metadata: ${viewResult.stderr.trim() || `exit code ${viewResult.exitCode}`}`, |
| 181 | ); |
| 182 | } |
| 183 | |
| 184 | // Resolve the patch. Primary: `gh pr diff` — one server-rendered document, |
| 185 | // perfect fidelity. GitHub refuses to render it for very large PRs (406 / |
| 186 | // "diff exceeded the maximum number of lines"); in that case fetch the same |
| 187 | // diff file-by-file from the paginated files API and stitch it back together. |
| 188 | let rawPatch: string; |
| 189 | let patchIncomplete = false; |
| 190 | if (diffResult.exitCode === 0) { |
| 191 | rawPatch = diffResult.stdout; |
| 192 | } else { |
| 193 | const filesResult = await runtime.runCommand("gh", hostnameArgs(ref.host, [ |
| 194 | "api", |
| 195 | `repos/${ref.owner}/${ref.repo}/pulls/${ref.number}/files?per_page=100`, |
| 196 | "--paginate", |
| 197 | ])); |
| 198 | if (filesResult.exitCode !== 0) { |
| 199 | const diffErr = diffResult.stderr.trim() || `exit code ${diffResult.exitCode}`; |
| 200 | const filesErr = filesResult.stderr.trim() || `exit code ${filesResult.exitCode}`; |
| 201 | throw new Error(`Failed to fetch PR diff (pr diff: ${diffErr}; files API: ${filesErr}).`); |
| 202 | } |
| 203 | const fileEntries = parsePaginatedArray<GitHubFileEntry>(filesResult.stdout); |
| 204 | rawPatch = reconstructGhPatch(fileEntries); |
| 205 | if (!rawPatch.trim()) { |
| 206 | throw new Error( |
| 207 | "PR diff is empty — it may be too large to fetch via the GitHub API. Review it on the GitHub web UI.", |
| 208 | ); |
| 209 | } |
| 210 | // The files API silently caps at 3000 files — never present a truncated |
| 211 | // review as complete. |
no test coverage detected