// SPEC: PJ2026-010606 GitHub入口 draft-2026-06-25-gh-split import { repoParts } from "./auth-and-safety"; import { commandError, errorPayload, githubGraphqlRequest, githubRequest, isGitHubError, validationError } from "./client"; import { prCompactSummary } from "./pr-summary"; import type { GitHubCommandResult, GitHubPullRequest } from "./types"; interface ReadyPullRequestGraphql { id?: string; number?: number; title?: string; state?: string; isDraft?: boolean; url?: string; headRefName?: string | null; baseRefName?: string | null; updatedAt?: string | null; } interface ReadyPullRequestMutation { markPullRequestReadyForReview?: { pullRequest?: ReadyPullRequestGraphql | null; } | null; } function mutationPlan(): Record { return { method: "POST", path: "/graphql", mutation: "markPullRequestReadyForReview", variables: { pullRequestId: "present" }, }; } function readyPullRequestSummary(pr: ReadyPullRequestGraphql, fallback: GitHubPullRequest): Record { return { id: pr.id ?? fallback.node_id ?? null, number: pr.number ?? fallback.number, title: pr.title ?? fallback.title, state: (pr.state ?? fallback.state).toLowerCase(), stateDetail: (pr.state ?? fallback.state).toLowerCase(), draft: pr.isDraft ?? false, url: pr.url ?? fallback.html_url, head: { ref: pr.headRefName ?? fallback.head?.ref ?? null, sha: fallback.head?.sha ?? null }, base: { ref: pr.baseRefName ?? fallback.base?.ref ?? null, sha: fallback.base?.sha ?? null }, headRefName: pr.headRefName ?? fallback.head?.ref ?? null, baseRefName: pr.baseRefName ?? fallback.base?.ref ?? null, updatedAt: pr.updatedAt ?? fallback.updated_at ?? null, }; } export async function prReady(repo: string, token: string, number: number, dryRun: boolean): Promise { const command = "pr ready"; const { owner, name } = repoParts(repo); const current = await githubRequest(token, "GET", `/repos/${owner}/${name}/pulls/${number}`); if (isGitHubError(current)) return commandError(command, repo, current, { number, phase: "fetch-pr" }); const pullRequest = prCompactSummary(current); if (current.state !== "open") { return validationError(command, repo, "只有 open 状态的 PR 才能转为 ready for review", { number, phase: "validate-pr-state", currentState: current.state, pullRequest, }); } if (typeof current.draft !== "boolean") { return commandError(command, repo, errorPayload("invalid-response", "GitHub REST PR 响应缺少 draft 布尔状态,无法安全判断是否需要写入"), { number, phase: "validate-pr-draft", pullRequest, }); } if (current.draft !== true) { return { ok: true, command, repo, number, dryRun, planned: false, changed: false, action: "already-ready", changedFields: [], pullRequest, rest: true, graphQl: false, }; } const request = mutationPlan(); if (dryRun) { return { ok: true, command, repo, number, dryRun: true, planned: true, changed: false, action: "would-mark-ready", changedFields: ["draft"], pullRequest, request, rest: true, graphQl: false, }; } if (typeof current.node_id !== "string" || current.node_id.length === 0) { return commandError(command, repo, errorPayload("invalid-response", "GitHub REST PR 响应缺少 markPullRequestReadyForReview 所需的 node_id"), { number, phase: "validate-pr-node-id", pullRequest, }); } const mutation = ` mutation MarkPullRequestReadyForReview($pullRequestId: ID!) { markPullRequestReadyForReview(input: { pullRequestId: $pullRequestId }) { pullRequest { id number title state isDraft url headRefName baseRefName updatedAt } } } `; const response = await githubGraphqlRequest(token, mutation, { pullRequestId: current.node_id }); if (isGitHubError(response)) { return commandError(command, repo, response, { number, phase: "mark-ready", pullRequest, request, }); } const ready = response.markPullRequestReadyForReview?.pullRequest; if (ready === null || ready === undefined || ready.isDraft !== false) { return commandError(command, repo, errorPayload("invalid-response", "GitHub GraphQL markPullRequestReadyForReview 响应未确认 isDraft=false", { details: response }), { number, phase: "verify-ready-response", pullRequest, request, }); } return { ok: true, command, repo, number: ready.number ?? number, dryRun: false, planned: false, changed: true, action: "marked-ready", changedFields: ["draft"], pullRequest: readyPullRequestSummary(ready, current), request, rest: true, graphQl: true, }; }