fix(cli): expose PR closeout metadata

Host commander merge after read-only audit. PR #72 is CLEAN/MERGEABLE and limited to UniDesk CLI PR closeout metadata: mergeability/status fields for gh pr read/view plus contract test/docs. Runner validation reported gh-cli-pr-contract-test and live metadata query; no HWLAB business code touched.
This commit is contained in:
Lyon
2026-05-23 00:35:06 +08:00
committed by GitHub
parent 8ebc9c7023
commit 2a8e9a5cd3
5 changed files with 216 additions and 24 deletions
+37 -1
View File
@@ -103,6 +103,30 @@ async function startMockGitHub(): Promise<{ baseUrl: string; requests: MockReque
sendJson(res, 200, pullRequest);
return;
}
if (req.method === "POST" && req.url === "/graphql") {
sendJson(res, 200, {
data: {
repository: {
pullRequest: {
mergeable: "MERGEABLE",
mergeStateStatus: "CLEAN",
headRefName: "feature/pr-contract",
baseRefName: "master",
statusCheckRollup: {
state: "SUCCESS",
contexts: {
nodes: [
{ __typename: "CheckRun", name: "contract", status: "COMPLETED", conclusion: "SUCCESS" },
{ __typename: "StatusContext", context: "legacy-ci", state: "SUCCESS", targetUrl: "https://ci.example.test/42", description: "ok" },
],
},
},
},
},
},
});
return;
}
if (req.method === "PATCH" && req.url === "/repos/pikasTech/unidesk/pulls/42") {
const parsed = JSON.parse(body) as JsonRecord;
sendJson(res, 200, { ...pullRequest, title: String(parsed.title ?? pullRequest.title), body: String(parsed.body ?? pullRequest.body), state: String(parsed.state ?? pullRequest.state), updated_at: "2026-05-20T06:00:00Z" });
@@ -180,6 +204,17 @@ export async function runGhCliPrContract(): Promise<JsonRecord> {
const viewSelected = viewData.json as JsonRecord;
assertCondition(viewSelected.body === "PR body" && viewSelected.title === "contract PR", "pr view alias should preserve selected fields", viewData);
const closeout = await runCli(["gh", "pr", "view", "42", "--repo", "pikasTech/unidesk", "--json", "mergeable,mergeStateStatus,statusCheckRollup,headRefName,baseRefName"], env);
assertCondition(closeout.status === 0, "pr view closeout metadata fields should not be rejected", closeout.json ?? { stdout: closeout.stdout });
const closeoutData = dataOf(closeout.json ?? {});
const closeoutJson = closeoutData.json as JsonRecord;
assertCondition(closeoutJson.mergeable === "MERGEABLE", "pr view should expose mergeable", closeoutData);
assertCondition(closeoutJson.mergeStateStatus === "CLEAN", "pr view should expose mergeStateStatus", closeoutData);
assertCondition(closeoutJson.headRefName === "feature/pr-contract" && closeoutJson.baseRefName === "master", "pr view should expose PR branch names", closeoutData);
const rollup = closeoutJson.statusCheckRollup as JsonRecord;
assertCondition(rollup.state === "SUCCESS", "pr view should expose statusCheckRollup", closeoutData);
assertCondition(mock.requests.some((request) => request.method === "POST" && request.url === "/graphql"), "closeout metadata should use GitHub GraphQL when requested", mock.requests);
const preflight = await runBun([
"scripts/code-queue-pr-preflight-example.ts",
"--repo",
@@ -206,7 +241,7 @@ export async function runGhCliPrContract(): Promise<JsonRecord> {
assertCondition(preflightComment.ok === true && preflightComment.dryRun === true && preflightComment.planned === true, "PR preflight comment must stay dry-run", preflightComment);
assertCondition(mock.requests.some((request) => request.method === "GET" && request.url === "/rate_limit"), "PR preflight should probe REST egress", mock.requests);
assertCondition(mock.requests.some((request) => request.method === "GET" && request.url === "/repos/pikasTech/unidesk"), "PR preflight should probe repo visibility", mock.requests);
assertCondition(mock.requests.every((request) => request.method === "GET"), "initial mock phase should remain read-only", mock.requests);
assertCondition(mock.requests.every((request) => request.method === "GET" || request.method === "POST" && request.url === "/graphql"), "initial mock phase should remain read-only except GraphQL metadata reads", mock.requests);
} finally {
await mock.close();
}
@@ -309,6 +344,7 @@ export async function runGhCliPrContract(): Promise<JsonRecord> {
checks: [
"gh help lists pr create/comment",
"pr list/read/view work through REST with token and no gh binary dependency",
"pr view closeout metadata fields are accepted and hydrated through GraphQL",
"pr create dry-run exposes planned operation",
"pr comment dry-run preserves markdown text",
"pr update replace/append and close/reopen are available",