import { spawn } from "node:child_process"; import { createServer, type IncomingMessage, type ServerResponse } from "node:http"; import type { AddressInfo } from "node:net"; import { writeFileSync, unlinkSync } from "node:fs"; import { join } from "node:path"; import { tmpdir } from "node:os"; type JsonRecord = Record; function assertCondition(condition: unknown, message: string, detail: unknown = {}): void { if (!condition) throw new Error(`${message}: ${JSON.stringify(detail)}`); } function runBun(args: string[], env: Record = {}): Promise<{ status: number | null; stdout: string; stderr: string; json: JsonRecord | null }> { return new Promise((resolve, reject) => { const child = spawn("bun", args, { cwd: process.cwd(), env: { ...process.env, ...env }, }); const stdoutChunks: Buffer[] = []; const stderrChunks: Buffer[] = []; child.stdout.on("data", (chunk) => stdoutChunks.push(Buffer.from(chunk))); child.stderr.on("data", (chunk) => stderrChunks.push(Buffer.from(chunk))); child.on("error", reject); child.on("close", (status) => { const stdout = Buffer.concat(stdoutChunks).toString("utf8"); let json: JsonRecord | null = null; try { json = JSON.parse(stdout) as JsonRecord; } catch { json = null; } resolve({ status, stdout, stderr: Buffer.concat(stderrChunks).toString("utf8"), json, }); }); }); } function runCli(args: string[], env: Record = {}): Promise<{ status: number | null; stdout: string; stderr: string; json: JsonRecord | null }> { return runBun(["scripts/cli.ts", ...args], env); } interface MockRequest { method: string; url: string; body: string; } function collectBody(req: IncomingMessage): Promise { return new Promise((resolve) => { const chunks: Buffer[] = []; req.on("data", (chunk) => chunks.push(Buffer.from(chunk))); req.on("end", () => resolve(Buffer.concat(chunks).toString("utf8"))); }); } function sendJson(res: ServerResponse, status: number, payload: unknown): void { res.statusCode = status; res.setHeader("content-type", "application/json"); res.end(JSON.stringify(payload)); } async function startMockGitHub(): Promise<{ baseUrl: string; requests: MockRequest[]; close: () => Promise }> { const requests: MockRequest[] = []; const pullRequest = { id: 4200, number: 42, title: "contract PR", body: "PR body", state: "open", html_url: "https://github.com/pikasTech/unidesk/pull/42", draft: false, user: { login: "runner" }, head: { ref: "feature/pr-contract", sha: "head-sha" }, base: { ref: "master", sha: "base-sha" }, created_at: "2026-05-20T04:00:00Z", updated_at: "2026-05-20T05:00:00Z", }; const server = createServer(async (req, res) => { const body = await collectBody(req); requests.push({ method: req.method ?? "", url: req.url ?? "", body }); if (req.method === "GET" && req.url === "/rate_limit") { sendJson(res, 200, { resources: { core: { limit: 5000, remaining: 4999 } } }); return; } if (req.method === "GET" && req.url === "/repos/pikasTech/unidesk") { sendJson(res, 200, { id: 1, full_name: "pikasTech/unidesk", private: true, default_branch: "master", permissions: { pull: true, push: true } }); return; } if (req.method === "GET" && req.url === "/repos/pikasTech/unidesk/issues?per_page=1&state=all") { sendJson(res, 200, []); return; } if (req.method === "GET" && req.url === "/repos/pikasTech/unidesk/pulls?state=all&per_page=4") { sendJson(res, 200, [pullRequest]); return; } if (req.method === "GET" && req.url === "/repos/pikasTech/unidesk/pulls/42") { sendJson(res, 200, pullRequest); 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" }); return; } if (req.method === "POST" && req.url === "/repos/pikasTech/unidesk/issues/42/comments") { const parsed = JSON.parse(body) as JsonRecord; 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 === "DELETE" && req.url === "/repos/pikasTech/unidesk/issues/comments/9101") { res.statusCode = 204; res.end(); return; } sendJson(res, 404, { message: "not found" }); }); await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve)); const address = server.address(); assertCondition(typeof address === "object" && address !== null, "mock server should expose address"); const port = (address as AddressInfo).port; assertCondition(typeof port === "number", "mock server should expose port"); return { baseUrl: `http://127.0.0.1:${port}`, requests, close: () => new Promise((resolve, reject) => server.close((error) => error ? reject(error) : resolve())), }; } function dataOf(response: JsonRecord): JsonRecord { assertCondition(response.ok === true, "CLI command should succeed", response); assertCondition(typeof response.data === "object" && response.data !== null && !Array.isArray(response.data), "response data should be object", response); return response.data as JsonRecord; } export async function runGhCliPrContract(): Promise { const help = await runCli(["gh", "help"]); assertCondition(help.status === 0, "gh help should succeed", help.json ?? { stdout: help.stdout }); const helpData = dataOf(help.json ?? {}); const usage = Array.isArray(helpData.usage) ? helpData.usage.map((value) => String(value)) : []; assertCondition(usage.some((line) => line.includes("gh pr create")), "gh help should list pr create", { usage }); assertCondition(usage.some((line) => line.includes("gh pr comment")), "gh help should list pr comment", { usage }); const mock = await startMockGitHub(); const env = { GH_TOKEN: "contract-token", UNIDESK_GITHUB_API_URL: mock.baseUrl, }; try { const list = await runCli(["gh", "pr", "list", "--repo", "pikasTech/unidesk", "--limit", "4"], env); assertCondition(list.status === 0, "pr list should succeed through REST", list.json ?? { stdout: list.stdout }); const listData = dataOf(list.json ?? {}); const pullRequests = listData.pullRequests as JsonRecord[]; assertCondition(Array.isArray(pullRequests) && pullRequests.length === 1, "pr list should return pullRequests", listData); assertCondition(pullRequests[0]?.number === 42 && pullRequests[0]?.base && pullRequests[0]?.head, "pr list should expose PR summary", pullRequests[0]); const view = await runCli(["gh", "pr", "view", "42", "--repo", "pikasTech/unidesk", "--json", "body,title,state,head,base"], env); assertCondition(view.status === 0, "pr view should succeed through REST", view.json ?? { stdout: view.stdout }); const viewData = dataOf(view.json ?? {}); const pullRequest = viewData.pullRequest as JsonRecord; assertCondition(pullRequest.number === 42 && pullRequest.url === "https://github.com/pikasTech/unidesk/pull/42", "pr view should expose PR details", viewData); const selected = viewData.json as JsonRecord; assertCondition(selected.body === "PR body" && selected.title === "contract PR", "pr view --json should select fields", viewData); const preflight = await runBun([ "scripts/code-queue-pr-preflight-example.ts", "--repo", "pikasTech/unidesk", "--base", "master", "--head", "feature/pr-contract", "--comment-pr", "42", ], env); assertCondition(preflight.status === 0, "PR preflight example should succeed against mock GitHub", preflight.json ?? { stdout: preflight.stdout }); assertCondition(preflight.json?.ok === true, "PR preflight example should report ok=true", preflight.json ?? {}); assertCondition(!preflight.stdout.includes("contract-token"), "PR preflight example must not print token values", { stdout: preflight.stdout }); assertCondition(typeof preflight.json?.checks === "object" && preflight.json.checks !== null && !Array.isArray(preflight.json.checks), "PR preflight should expose checks", preflight.json ?? {}); const preflightChecks = preflight.json?.checks as JsonRecord; const envToken = preflightChecks.envToken as JsonRecord; assertCondition(envToken.present === true && envToken.source === "GH_TOKEN", "PR preflight should require env token source", envToken); const authStatus = preflightChecks.githubAuthStatus as JsonRecord; assertCondition(authStatus.ok === true, "PR preflight should prove GitHub REST egress and repo visibility", authStatus); const preflightCreate = preflightChecks.prCreateDryRun as JsonRecord; const preflightComment = preflightChecks.prCommentDryRun as JsonRecord; assertCondition(preflightCreate.ok === true && preflightCreate.dryRun === true && preflightCreate.planned === true, "PR preflight create must stay dry-run", preflightCreate); 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); } finally { await mock.close(); } const title = "contract pr create"; const bodyFile = join(tmpdir(), `unidesk-gh-pr-contract-${process.pid}.md`); writeFileSync(bodyFile, "Line 1\n`code`\n| a | b |\n", "utf8"); try { const createDryRun = await runCli(["gh", "pr", "create", "--repo", "pikasTech/unidesk", "--title", title, "--body-file", bodyFile, "--base", "master", "--head", "feature/pr-contract", "--draft", "--dry-run"]); assertCondition(createDryRun.status === 0, "pr create dry-run should succeed", createDryRun.json ?? { stdout: createDryRun.stdout }); const createData = dataOf(createDryRun.json ?? {}); assertCondition(createData.dryRun === true, "dry-run create must set dryRun=true", createData); assertCondition(createData.planned === true, "dry-run create must set planned=true", createData); assertCondition(createData.repo === "pikasTech/unidesk", "dry-run create should preserve repo", createData); assertCondition(createData.base === "master", "dry-run create should preserve base", createData); assertCondition(createData.head === "feature/pr-contract", "dry-run create should preserve head", createData); assertCondition(createData.draft === true, "dry-run create should preserve draft", createData); assertCondition(Number(createData.bodyChars ?? 0) > 0, "dry-run create should expose bodyChars", createData); assertCondition(Array.isArray(createData.bodyPreviewLines), "dry-run create should expose bodyPreviewLines", createData); assertCondition(String(createData.bodyPreview ?? "").includes("`code`"), "dry-run create should preserve backticks in preview", createData); assertCondition(createData.request && typeof createData.request === "object", "dry-run create should include request plan", createData); const commentDryRun = await runCli(["gh", "pr", "comment", "42", "--repo", "pikasTech/unidesk", "--body-file", bodyFile, "--dry-run"]); assertCondition(commentDryRun.status === 0, "pr comment dry-run should succeed", commentDryRun.json ?? { stdout: commentDryRun.stdout }); const commentData = dataOf(commentDryRun.json ?? {}); assertCondition(commentData.dryRun === true, "dry-run comment must set dryRun=true", commentData); assertCondition(commentData.planned === true, "dry-run comment must set planned=true", commentData); assertCondition(commentData.issueNumber === 42, "dry-run comment should preserve PR number", commentData); assertCondition(Number(commentData.bodyChars ?? 0) > 0, "dry-run comment should expose bodyChars", commentData); const mock2 = await startMockGitHub(); const env2 = { GH_TOKEN: "contract-token", UNIDESK_GITHUB_API_URL: mock2.baseUrl, }; try { const updateReplace = await runCli(["gh", "pr", "update", "42", "--repo", "pikasTech/unidesk", "--mode", "replace", "--body-file", bodyFile, "--title", "updated"], env2); assertCondition(updateReplace.status === 0, "pr update replace should succeed", updateReplace.json ?? { stdout: updateReplace.stdout }); const updateReplaceData = dataOf(updateReplace.json ?? {}); assertCondition(updateReplaceData.command === "pr update" && updateReplaceData.mode === "replace", "pr update replace should report mode", updateReplaceData); const updateAppend = await runCli(["gh", "pr", "update", "42", "--repo", "pikasTech/unidesk", "--mode", "append", "--body-file", bodyFile, "--dry-run"], env2); assertCondition(updateAppend.status === 0, "pr update append dry-run should succeed", updateAppend.json ?? { stdout: updateAppend.stdout }); const updateAppendData = dataOf(updateAppend.json ?? {}); const finalBody = updateAppendData.finalBody as JsonRecord; assertCondition(updateAppendData.mode === "append", "pr append mode should be explicit", updateAppendData); assertCondition(finalBody.containsBackticks === true && finalBody.containsMarkdownTable === true, "pr append should preserve markdown signals", updateAppendData); const closePr = await runCli(["gh", "pr", "close", "42", "--repo", "pikasTech/unidesk"], env2); assertCondition(closePr.status === 0, "pr close should succeed", closePr.json ?? { stdout: closePr.stdout }); const closeData = dataOf(closePr.json ?? {}); assertCondition(closeData.command === "pr close", "pr close command should be explicit", closeData); const reopenPr = await runCli(["gh", "pr", "reopen", "42", "--repo", "pikasTech/unidesk", "--dry-run"], env2); assertCondition(reopenPr.status === 0, "pr reopen dry-run should succeed", reopenPr.json ?? { stdout: reopenPr.stdout }); const reopenData = dataOf(reopenPr.json ?? {}); assertCondition(reopenData.command === "pr reopen" && reopenData.dryRun === true, "pr reopen dry-run should be explicit", reopenData); const commentCreate = await runCli(["gh", "pr", "comment", "create", "42", "--repo", "pikasTech/unidesk", "--body-file", bodyFile], env2); assertCondition(commentCreate.status === 0, "pr comment create should succeed", commentCreate.json ?? { stdout: commentCreate.stdout }); const commentCreateData = dataOf(commentCreate.json ?? {}); assertCondition(commentCreateData.command === "pr comment create", "pr comment create should use CRUD command name", commentCreateData); 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 ?? {}); assertCondition(commentDeleteData.deleted === true, "pr comment delete should report deleted", commentDeleteData); } finally { await mock2.close(); } const mergeBlocked = await runCli(["gh", "pr", "merge", "42", "--repo", "pikasTech/unidesk"]); assertCondition(mergeBlocked.status !== 0, "pr merge should fail", mergeBlocked.json ?? { stdout: mergeBlocked.stdout }); const mergeData = mergeBlocked.json?.data as JsonRecord | undefined; assertCondition(String(mergeData?.message ?? "").includes("intentionally unsupported"), "merge block message should be explicit", mergeData ?? {}); assertCondition(mergeData?.runnerDisposition === "business-failed", "merge block should classify as business-failed", mergeData ?? {}); const deleteBlocked = await runCli(["gh", "pr", "delete", "42", "--repo", "pikasTech/unidesk"]); assertCondition(deleteBlocked.status !== 0, "pr hard delete should fail", deleteBlocked.json ?? { stdout: deleteBlocked.stdout }); const deleteData = deleteBlocked.json?.data as JsonRecord | undefined; assertCondition(deleteData?.degradedReason === "unsupported-command", "pr delete should be unsupported-command", deleteData ?? {}); const createMissingBody = await runCli(["gh", "pr", "create", "--repo", "pikasTech/unidesk", "--title", title, "--base", "master", "--head", "feature/pr-contract", "--dry-run"]); assertCondition(createMissingBody.status !== 0, "pr create without body source should fail", createMissingBody.json ?? { stdout: createMissingBody.stdout }); const createMissingBodyData = createMissingBody.json?.data as JsonRecord | undefined; assertCondition(createMissingBodyData?.degradedReason === "validation-failed", "missing body source should be validation-failed", createMissingBodyData ?? {}); assertCondition(createMissingBodyData?.runnerDisposition === "business-failed", "validation should classify as business-failed", createMissingBodyData ?? {}); const unknownOption = await runCli(["gh", "pr", "create", "--repo", "pikasTech/unidesk", "--title", title, "--body-file", bodyFile, "--base", "master", "--head", "feature/pr-contract", "--dry-run", "--bad-option"]); assertCondition(unknownOption.status !== 0, "unknown gh option should fail", unknownOption.json ?? { stdout: unknownOption.stdout }); const unknownOptionData = unknownOption.json?.data as JsonRecord | undefined; assertCondition(unknownOptionData?.degradedReason === "unsupported-command", "unknown option should be unsupported-command", unknownOptionData ?? {}); assertCondition(unknownOptionData?.runnerDisposition === "business-failed", "unknown option should classify as business-failed", unknownOptionData ?? {}); } finally { unlinkSync(bodyFile); } return { ok: true, checks: [ "gh help lists pr create/comment", "pr list/view work through REST with token and no gh binary dependency", "pr create dry-run exposes planned operation", "pr comment dry-run preserves markdown text", "pr update replace/append and close/reopen are available", "pr comment create/delete follows CRUD shape", "pr merge is blocked", "pr hard delete is blocked", "pr create validation failures are structured", "unknown gh options are structured", ], }; } if (import.meta.main) { const result = await runGhCliPrContract(); process.stdout.write(`${JSON.stringify(result, null, 2)}\n`); }