Files
gitea-axi/src/render.ts
alexion 16d2f29f19
All checks were successful
CI / test (pull_request) Successful in 36s
CI / test (push) Successful in 37s
feat: add pr view and checks (task 0009)
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`.
2026-07-12 20:22:17 -04:00

80 lines
2.4 KiB
TypeScript

import { encode } from "@toon-format/toon";
// Same shape the SDK's own error formatter produces; its renderError helper
// exists in axi-sdk-js but is not exported from the package index.
export function renderErrorOutput(
message: string,
code: string,
suggestions: string[] = [],
): string {
const output: Record<string, unknown> = { error: message, code };
if (suggestions.length > 0) {
output.help = suggestions;
}
return encode(output);
}
export function formatCountLine(
shown: number,
total: number | undefined,
atLimit: boolean,
): string {
if (total === undefined) {
if (atLimit) {
return `count: ${shown} (showing first ${shown})`;
}
return `count: ${shown} of ${shown} total`;
}
return `count: ${shown} of ${total} total`;
}
/** Encode a named list block, with an explicit empty-state line when there are no rows. */
function encodeRows(noun: string, rows: Record<string, unknown>[]): string {
return rows.length > 0 ? encode({ [noun]: rows }) : `${noun}[0]: (none)`;
}
export interface RenderListOptions {
noun: string;
rows: Record<string, unknown>[];
countLine: string;
help: string[];
}
export function renderList(options: RenderListOptions): string {
const body = encodeRows(options.noun, options.rows);
return [options.countLine, body, encode({ help: options.help })].join("\n");
}
/**
* A literal `noun: value` line followed by the help block — for output whose
* entity is a single line rather than a field map or a list (e.g. the no-CI
* `checks:` message). The value is emitted verbatim, not re-encoded: TOON reads
* a string scalar to end of line, so a comma-bearing message stays unquoted,
* matching the way a summary line stands above a list block.
*/
export function renderScalar(noun: string, value: string, help: string[]): string {
return [`${noun}: ${value}`, encode({ help })].join("\n");
}
/** A secondary list block appended below a detail entity (e.g. comments). */
export interface DetailBlock {
noun: string;
rows: Record<string, unknown>[];
}
export interface RenderDetailOptions {
noun: string;
item: Record<string, unknown>;
blocks?: DetailBlock[];
help: string[];
}
export function renderDetail(options: RenderDetailOptions): string {
const parts = [encode({ [options.noun]: options.item })];
for (const block of options.blocks ?? []) {
parts.push(encodeRows(block.noun, block.rows));
}
parts.push(encode({ help: options.help }));
return parts.join("\n");
}