Files
pikasTech-unidesk/scripts/src/gh/repo-compare.ts
T
2026-06-26 00:16:53 +08:00

138 lines
4.7 KiB
TypeScript

// SPEC: PJ2026-010606 GitHub入口 draft-2026-06-25-gh-split
// Moved mechanically from scripts/src/gh.ts:5552-5680.
import path from "node:path";
import { bodySafetySignals, bodySha, preview, previewLines, repoParts } from "./auth-and-safety";
import { githubRequest } from "./client";
import type { BodyUpdateMode, GitHubBranch, GitHubCompare, GitHubErrorPayload, GitHubOptions, GitHubRepository } from "./types";
export function repoSummary(repo: GitHubRepository): Record<string, unknown> {
return {
id: repo.id ?? null,
fullName: repo.full_name ?? null,
private: repo.private ?? null,
defaultBranch: repo.default_branch ?? null,
permissions: repo.permissions ?? null,
};
}
export function branchSummary(branch: GitHubBranch): Record<string, unknown> {
return {
name: branch.name ?? null,
sha: branch.commit?.sha ?? null,
};
}
export function compareSummary(compare: GitHubCompare): Record<string, unknown> {
return {
status: compare.status ?? null,
aheadBy: compare.ahead_by ?? null,
behindBy: compare.behind_by ?? null,
totalCommits: compare.total_commits ?? null,
htmlUrl: compare.html_url ?? null,
baseSha: compare.base_commit?.sha ?? null,
mergeBaseSha: compare.merge_base_commit?.sha ?? null,
};
}
export function prUrl(repo: string, number: number): string {
return `https://github.com/${repo}/pull/${number}`;
}
export function encodePathPart(value: string): string {
return encodeURIComponent(value);
}
export async function repoInfo(token: string, repo: string): Promise<GitHubRepository | GitHubErrorPayload> {
const { owner, name } = repoParts(repo);
return githubRequest<GitHubRepository>(token, "GET", `/repos/${owner}/${name}`);
}
export async function branchInfo(token: string, repo: string, branch: string): Promise<GitHubBranch | GitHubErrorPayload> {
const { owner, name } = repoParts(repo);
return githubRequest<GitHubBranch>(token, "GET", `/repos/${owner}/${name}/branches/${encodePathPart(branch)}`);
}
export async function compareBranches(token: string, repo: string, base: string, head: string): Promise<GitHubCompare | GitHubErrorPayload> {
const { owner, name } = repoParts(repo);
return githubRequest<GitHubCompare>(token, "GET", `/repos/${owner}/${name}/compare/${encodePathPart(base)}...${encodePathPart(head)}`);
}
export function prCreatePlannedOperation(repo: string, options: GitHubOptions, body: string, bodySource: Record<string, unknown>): Record<string, unknown> {
return {
repo,
title: options.title,
base: options.base,
head: options.head,
draft: options.draft,
bodyChars: body.length,
bodyPreview: preview(body),
bodyPreviewLines: body.split(/\r?\n/).slice(0, 12),
bodySource,
preservesRawNewlines: body.includes("\n"),
containsLiteralBackslashN: body.includes("\\n"),
containsBackticks: body.includes("`"),
request: {
method: "POST",
path: "/repos/{owner}/{repo}/pulls",
body: {
title: options.title,
base: options.base,
head: options.head,
draft: options.draft,
bodyChars: body.length,
},
},
validation: {
repo: "required",
base: "required",
head: "required",
title: "required",
body: body.length > 0 ? "optional" : "empty",
},
};
}
export function prCommentPlannedOperation(repo: string, issueNumber: number, body: string, bodySource: Record<string, unknown>): Record<string, unknown> {
return {
repo,
issueNumber,
bodyChars: body.length,
bodyPreview: preview(body),
bodyPreviewLines: body.split(/\r?\n/).slice(0, 12),
bodySource,
preservesRawNewlines: body.includes("\n"),
containsLiteralBackslashN: body.includes("\\n"),
containsBackticks: body.includes("`"),
request: {
method: "POST",
path: `/repos/{owner}/{repo}/issues/${issueNumber}/comments`,
body: { bodyChars: body.length },
},
validation: {
repo: "required",
issueNumber: "required",
body: "required",
},
};
}
export function bodyUpdatePlan(command: string, repo: string, number: number, mode: BodyUpdateMode, body: string, bodySource: Record<string, unknown>, existingBody: string | null): Record<string, unknown> {
const finalBody = mode === "append" ? `${existingBody ?? ""}${body}` : body;
return {
command,
repo,
number,
mode,
bodySource,
input: bodySafetySignals(body),
existingBody: existingBody === null ? { fetched: false } : { fetched: true, bodyChars: existingBody.length, bodySha: bodySha(existingBody) },
finalBody: {
bodyChars: finalBody.length,
bodyPreview: preview(finalBody),
bodyPreviewLines: previewLines(finalBody),
...bodySafetySignals(finalBody),
},
};
}