fix(cli): bound large gh output
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { spawn } from "node:child_process";
|
||||
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
||||
import { existsSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { createServer, type IncomingMessage, type ServerResponse } from "node:http";
|
||||
@@ -218,6 +218,16 @@ async function startMockGitHub(): Promise<{ baseUrl: string; requests: MockReque
|
||||
body: "# 普通任务\n\n## 背景\n\n- 不是指挥简报。\n",
|
||||
html_url: "https://github.com/pikasTech/unidesk/issues/47",
|
||||
};
|
||||
const largeIssueBody = Array.from({ length: 900 }, (_, index) => `large-output-line-${String(index + 1).padStart(4, "0")} ${"x".repeat(60)}`).join("\n");
|
||||
const largeIssue = {
|
||||
...issue,
|
||||
id: 2090,
|
||||
number: 90,
|
||||
title: "large issue output fixture",
|
||||
body: largeIssueBody,
|
||||
html_url: "https://github.com/pikasTech/unidesk/issues/90",
|
||||
updated_at: "2026-05-20T09:00:00Z",
|
||||
};
|
||||
const issueList = [
|
||||
{
|
||||
id: 2001,
|
||||
@@ -459,6 +469,10 @@ async function startMockGitHub(): Promise<{ baseUrl: string; requests: MockReque
|
||||
sendJson(res, 200, nonBriefIssue);
|
||||
return;
|
||||
}
|
||||
if (req.method === "GET" && req.url === "/repos/pikasTech/unidesk/issues/90") {
|
||||
sendJson(res, 200, largeIssue);
|
||||
return;
|
||||
}
|
||||
if (req.method === "GET" && req.url === "/repos/pikasTech/unidesk/issues/60") {
|
||||
sendJson(res, 200, legacyBoardIssue);
|
||||
return;
|
||||
@@ -471,6 +485,10 @@ async function startMockGitHub(): Promise<{ baseUrl: string; requests: MockReque
|
||||
sendJson(res, 200, comments);
|
||||
return;
|
||||
}
|
||||
if (req.method === "GET" && req.url === "/repos/pikasTech/unidesk/issues/90/comments?per_page=100") {
|
||||
sendJson(res, 200, []);
|
||||
return;
|
||||
}
|
||||
if (req.method === "GET" && req.url === "/repos/pikasTech/HWLAB/issues/7/comments?per_page=100") {
|
||||
sendJson(res, 200, [{ id: 7001, body: "shorthand comment", html_url: "https://github.com/pikasTech/HWLAB/issues/7#issuecomment-7001", user: { login: "tester" }, created_at: "2026-05-20T03:10:00Z", updated_at: "2026-05-20T03:10:00Z" }]);
|
||||
return;
|
||||
@@ -645,6 +663,19 @@ export async function runGhCliIssueGuardContract(): Promise<JsonRecord> {
|
||||
assertCondition(Array.isArray(firstLabels) && firstLabels[0]?.name === "cli", "issue list default fields should include labels", listDefaultData);
|
||||
assertCondition(defaultIssues.every((item) => typeof item.number === "number" && typeof item.url === "string"), "issue list default fields should expose stable JSON", listDefaultData);
|
||||
|
||||
const largeRead = await runCli(["gh", "issue", "read", "90", "--repo", "pikasTech/unidesk", "--full"], env);
|
||||
assertCondition(largeRead.status === 0, "large issue read should succeed", largeRead.json ?? { stdout: largeRead.stdout });
|
||||
assertCondition(largeRead.stdout.length < 20_000, "large issue read stdout should stay bounded", { bytes: largeRead.stdout.length });
|
||||
const largeReadData = dataOf(largeRead.json ?? {});
|
||||
assertCondition(largeReadData.outputTruncated === true, "large issue read should be dumped instead of printed fully", largeReadData);
|
||||
const dump = largeReadData.dump as JsonRecord;
|
||||
assertCondition(typeof dump.path === "string" && existsSync(String(dump.path)), "large issue dump file should exist", dump);
|
||||
assertCondition(Number(dump.bytes ?? 0) > 20_000, "dump should record full output size", dump);
|
||||
assertCondition(Number(dump.lines ?? 0) > 20, "dump should record total line count", dump);
|
||||
assertCondition(String(dump.head ?? "").length > 0 && String(dump.tail ?? "").length > 0, "dump should include head and tail previews", dump);
|
||||
const dumpText = readFileSync(String(dump.path), "utf8");
|
||||
assertCondition(dumpText.includes("large-output-line-0900"), "dump file should contain full original JSON", { path: dump.path, tail: dumpText.slice(-500) });
|
||||
|
||||
const scanEscape = await runCli(["gh", "issue", "scan-escape", "--repo", "pikasTech/unidesk", "--limit", "4", "--dry-run"], env);
|
||||
assertCondition(scanEscape.status === 0, "issue scan-escape dry-run should succeed", scanEscape.json ?? { stdout: scanEscape.stdout });
|
||||
const scanData = dataOf(scanEscape.json ?? {});
|
||||
@@ -1465,6 +1496,7 @@ export async function runGhCliIssueGuardContract(): Promise<JsonRecord> {
|
||||
"issue list supports state/limit/json with stable selected fields",
|
||||
"acceptance issue list command succeeds under mock GitHub",
|
||||
"issue list default fields include labels and filter pull requests",
|
||||
"large gh issue read output is dumped to a temp file with bounded stdout and head/tail metadata",
|
||||
"issue scan-escape classifies pollution, explanatory mentions, and body risks",
|
||||
"issue cleanup-plan remains dry-run with body/comment cleanup suggestions",
|
||||
"issue board-audit returns read-only board structure, disables OPEN/CLOSED coverage validation, and keeps compatibility fields empty without writes",
|
||||
|
||||
Reference in New Issue
Block a user