fix: 支持 GitHub 评论原地编辑

This commit is contained in:
Codex
2026-06-09 10:09:33 +00:00
parent 195d33dbb5
commit 35eb888710
6 changed files with 194 additions and 27 deletions
+34 -2
View File
@@ -242,6 +242,11 @@ async function startMockGitHub(): Promise<{ baseUrl: string; requests: MockReque
sendJson(res, 201, { id: 9101, body: String(parsed.body ?? ""), html_url: "https://github.com/pikasTech/unidesk/pull/42#issuecomment-9101", user: { login: "runner" }, created_at: "2026-05-20T06:10:00Z", updated_at: "2026-05-20T06:10:00Z" });
return;
}
if (req.method === "PATCH" && req.url === "/repos/pikasTech/unidesk/issues/comments/9101") {
const parsed = JSON.parse(body) as JsonRecord;
sendJson(res, 200, { id: 9101, body: String(parsed.body ?? ""), html_url: "https://github.com/pikasTech/unidesk/pull/42#issuecomment-9101", user: { login: "runner" }, created_at: "2026-05-20T06:10:00Z", updated_at: "2026-05-20T06:12:00Z" });
return;
}
if (req.method === "DELETE" && req.url === "/repos/pikasTech/unidesk/issues/comments/9101") {
res.statusCode = 204;
res.end();
@@ -311,7 +316,7 @@ export async function runGhCliPrContract(): Promise<JsonRecord> {
assertCondition(notes.some((line) => line.includes("PR view is the canonical")), "gh help should state pr view is canonical", { notes });
assertCondition(notes.some((line) => line.includes("read remains") && line.includes("compatibility alias")), "gh help should state pr read is alias", { notes });
assertCondition(notes.some((line) => line.includes("GitHub PR URLs") && line.includes("owner/repo#number shorthand")), "gh help should explain pr view/read URL and shorthand targets", { notes });
assertCondition(notes.some((line) => line.includes("--number is accepted on single PR/comment numeric target commands") && line.includes("PR comment delete treats --number as commentId")), "gh help should document --number compatibility hint", { notes });
assertCondition(notes.some((line) => line.includes("--number is accepted on single PR/comment numeric target commands") && line.includes("PR comment update/edit/delete treat --number as commentId")), "gh help should document --number compatibility hint", { notes });
assertCondition(notes.some((line) => line.includes("--raw and --full are explicit full-disclosure aliases")), "gh help should explain raw/full read disclosure", { notes });
assertCondition(notes.some((line) => line.includes("PR list defaults to --state all")), "gh help should document pr list default state", { notes });
assertCondition(notes.some((line) => line.includes("stateDetail") && line.includes("mergedAt")), "gh help should describe closeout field normalization", { notes });
@@ -741,6 +746,33 @@ export async function runGhCliPrContract(): Promise<JsonRecord> {
const prInlinePayload = JSON.parse(prInlineCommentRequest?.body ?? "{}") as JsonRecord;
assertCondition(prInlinePayload.body === prInlineCommentBody, "pr inline comment payload should preserve --body text", prInlinePayload);
const prCommentUpdateBody = "PR 评论原地修正";
const prCommentUpdateDryRunRequestCountBefore = mock2.requests.length;
const prCommentUpdateDryRun = await runCli(["gh", "pr", "comment", "update", "9101", "--repo", "pikasTech/unidesk", "--body", prCommentUpdateBody, "--dry-run"], env2);
assertCondition(prCommentUpdateDryRun.status === 0, "pr comment update dry-run should succeed", prCommentUpdateDryRun.json ?? { stdout: prCommentUpdateDryRun.stdout });
assertCondition(prCommentUpdateDryRun.json?.command === "gh pr comment update 9101 --repo pikasTech/unidesk --body <body:redacted> --dry-run", "outer gh command should redact PR comment update inline body", prCommentUpdateDryRun.json ?? {});
const prCommentUpdateDryRunData = dataOf(prCommentUpdateDryRun.json ?? {});
assertCondition(prCommentUpdateDryRunData.command === "pr comment update" && prCommentUpdateDryRunData.commentId === 9101 && prCommentUpdateDryRunData.dryRun === true, "pr comment update dry-run should report commentId", prCommentUpdateDryRunData);
const prCommentUpdateRequest = prCommentUpdateDryRunData.request as JsonRecord;
assertCondition(prCommentUpdateRequest.method === "PATCH" && String(prCommentUpdateRequest.path ?? "").includes("/issues/comments/{comment_id}"), "pr comment update dry-run should plan PATCH comment endpoint", prCommentUpdateRequest);
const prCommentUpdateDryRunWriteCount = mock2.requests.slice(prCommentUpdateDryRunRequestCountBefore).filter((request) => request.method === "PATCH" && request.url.includes("/issues/comments/")).length;
assertCondition(prCommentUpdateDryRunWriteCount === 0, "pr comment update dry-run must not PATCH GitHub", { requests: mock2.requests.slice(prCommentUpdateDryRunRequestCountBefore) });
const prCommentEditBody = "PR edit 别名\n\n- 保留 `code`\n";
const prCommentEditRequestCountBefore = mock2.requests.length;
const prCommentEdit = await runCli(["gh", "pr", "comment", "edit", "--number", "9101", "--repo", "pikasTech/unidesk", "--body-stdin"], env2, prCommentEditBody);
assertCondition(prCommentEdit.status === 0, "pr comment edit should accept --number compatibility alias and stdin", prCommentEdit.json ?? { stdout: prCommentEdit.stdout });
const prCommentEditData = dataOf(prCommentEdit.json ?? {});
assertCondition(prCommentEditData.command === "pr comment edit" && prCommentEditData.commentId === 9101, "pr comment edit should report alias command and commentId", prCommentEditData);
const prCommentEditHint = prCommentEditData.standardSyntaxHint as JsonRecord;
assertCondition(String(prCommentEditHint.standardCommand ?? "").includes("gh pr comment edit 9101 --repo pikasTech/unidesk"), "pr comment edit --number should point to positional commentId syntax", prCommentEditHint);
const prCommentEditSummary = prCommentEditData.comment as JsonRecord;
assertCondition(prCommentEditSummary.id === 9101 && prCommentEditSummary.bodyOmitted === true && !("body" in prCommentEditSummary), "pr comment edit should preserve id and omit full body", prCommentEditSummary);
const prCommentEditPatch = mock2.requests.slice(prCommentEditRequestCountBefore).find((request) => request.method === "PATCH" && request.url === "/repos/pikasTech/unidesk/issues/comments/9101");
assertCondition(prCommentEditPatch !== undefined, "pr comment edit should PATCH issue comments endpoint", { requests: mock2.requests.slice(prCommentEditRequestCountBefore) });
const prCommentEditPayload = JSON.parse(prCommentEditPatch?.body ?? "{}") as JsonRecord;
assertCondition(prCommentEditPayload.body === prCommentEditBody, "pr comment edit payload should preserve stdin Markdown", prCommentEditPayload);
const commentDelete = await runCli(["gh", "pr", "comment", "delete", "9101", "--repo", "pikasTech/unidesk"], env2);
assertCondition(commentDelete.status === 0, "pr comment delete should succeed", commentDelete.json ?? { stdout: commentDelete.stdout });
const commentDeleteData = dataOf(commentDelete.json ?? {});
@@ -799,7 +831,7 @@ export async function runGhCliPrContract(): Promise<JsonRecord> {
"pr update/edit use low-noise REST PATCH without GraphQL projectCards",
"pr edit supports --body-stdin without echoing full body",
"pr update append and close/reopen are available",
"pr comment create/delete follows CRUD shape, --body-stdin, and --body remains supported",
"pr comment create/update/edit/delete follows CRUD shape, --body-stdin, and --body remains supported",
"pr merge is guarded by preflight and uses REST",
"pr hard delete is blocked",
"pr create validation failures are structured",