Add `pr view <n>` and `pr checks <n>`, built on the truncation machinery
and review fetches from earlier slices.
`pr view` uses the three-call fetch pattern (PR + reviews in parallel, then
the head commit's combined status) so `checks`, `comment_count`, and
`review_count` are in the default output; `--comments`, `--reviews`, and
`--full` behave as on `issue view`, with `--reviews` exposing Gitea's
`official`/`stale` fields plus per-review inline comments.
`pr checks <n>` renders the checks summary line and the `{ name, conclusion }`
rows, or the scalar no-CI message when no statuses exist.
The state→conclusion mapping and summary live in a new `src/checks.ts`;
`commentRows` is extracted into `src/comment.ts` and shared with `issue view`.
103 lines
3.3 KiB
TypeScript
103 lines
3.3 KiB
TypeScript
import type { CombinedStatus, CommitStatus } from "gitea-js";
|
|
import type { GiteaClient } from "./client.js";
|
|
import type { RepoContext } from "./context.js";
|
|
import { classifyHttpError } from "./errors.js";
|
|
|
|
/**
|
|
* A PR's CI checks, derived from its commit statuses. Shared by `pr view` (which
|
|
* renders only the {@link ChecksResult.summary} line) and `pr checks` (which
|
|
* renders the summary plus the per-check rows).
|
|
*/
|
|
|
|
/** gh-axi's four-value check classification (see the gitea-axi spec, "pr checks"). */
|
|
export type CheckConclusion = "pass" | "fail" | "skip" | "pending";
|
|
|
|
/** The summary shown when a PR has no commit statuses at all. */
|
|
export const NO_CHECKS_MESSAGE =
|
|
"0 passed, 0 failed — this PR has no CI checks configured";
|
|
|
|
export interface CheckRow {
|
|
name: string;
|
|
conclusion: CheckConclusion;
|
|
}
|
|
|
|
export interface ChecksResult {
|
|
/** The `N passed, N failed[, N skipped][, N pending], N total` line, or {@link NO_CHECKS_MESSAGE}. */
|
|
summary: string;
|
|
checks: CheckRow[];
|
|
}
|
|
|
|
/**
|
|
* Map a Gitea commit-status state to a check conclusion. `warning` counts as a
|
|
* failure, matching Gitea's own status-combine logic; `skipped` (only emitted by
|
|
* newer instances) is its own bucket. Anything else — `pending` and any state a
|
|
* future Gitea might add — is treated as still-in-progress rather than reported
|
|
* as a pass or a failure it is not.
|
|
*/
|
|
function classifyState(state: string | undefined): CheckConclusion {
|
|
switch (state) {
|
|
case "success":
|
|
return "pass";
|
|
case "failure":
|
|
case "error":
|
|
case "warning":
|
|
return "fail";
|
|
case "skipped":
|
|
return "skip";
|
|
default:
|
|
return "pending";
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Reduce a PR's commit statuses to its checks summary and per-check rows. The
|
|
* `skipped` and `pending` counts are folded into the summary line only when
|
|
* non-zero, so an all-passing PR reads `N passed, 0 failed, N total`.
|
|
*/
|
|
export function summarizeChecks(statuses: CommitStatus[]): ChecksResult {
|
|
const checks: CheckRow[] = statuses.map((status) => ({
|
|
name: status.context ?? "",
|
|
conclusion: classifyState(status.status),
|
|
}));
|
|
return { summary: summaryLine(checks), checks };
|
|
}
|
|
|
|
function summaryLine(checks: CheckRow[]): string {
|
|
if (checks.length === 0) {
|
|
return NO_CHECKS_MESSAGE;
|
|
}
|
|
const count = (conclusion: CheckConclusion): number =>
|
|
checks.filter((check) => check.conclusion === conclusion).length;
|
|
const parts = [`${count("pass")} passed`, `${count("fail")} failed`];
|
|
const skipped = count("skip");
|
|
if (skipped > 0) {
|
|
parts.push(`${skipped} skipped`);
|
|
}
|
|
const pending = count("pending");
|
|
if (pending > 0) {
|
|
parts.push(`${pending} pending`);
|
|
}
|
|
parts.push(`${checks.length} total`);
|
|
return parts.join(", ");
|
|
}
|
|
|
|
/**
|
|
* Fetch a PR head SHA's combined commit status and reduce it to its checks
|
|
* summary and rows. One HTTP call — `pr view` issues it once the head SHA is
|
|
* known, after the PR and reviews fetches (ADR 0006's three-call pattern).
|
|
*/
|
|
export async function fetchChecks(
|
|
api: GiteaClient,
|
|
context: RepoContext,
|
|
sha: string,
|
|
): Promise<ChecksResult> {
|
|
let combined: CombinedStatus;
|
|
try {
|
|
const response = await api.repos.repoGetCombinedStatusByRef(context.owner, context.name, sha);
|
|
combined = response.data;
|
|
} catch (error) {
|
|
throw classifyHttpError(error);
|
|
}
|
|
return summarizeChecks(combined.statuses ?? []);
|
|
}
|