170 lines
5.0 KiB
TypeScript
170 lines
5.0 KiB
TypeScript
// 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<string, unknown> {
|
|
return {
|
|
method: "POST",
|
|
path: "/graphql",
|
|
mutation: "markPullRequestReadyForReview",
|
|
variables: { pullRequestId: "present" },
|
|
};
|
|
}
|
|
|
|
function readyPullRequestSummary(pr: ReadyPullRequestGraphql, fallback: GitHubPullRequest): Record<string, unknown> {
|
|
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<GitHubCommandResult> {
|
|
const command = "pr ready";
|
|
const { owner, name } = repoParts(repo);
|
|
const current = await githubRequest<GitHubPullRequest>(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<ReadyPullRequestMutation>(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,
|
|
};
|
|
}
|