fix(cli): support gh read shorthand raw
This commit is contained in:
@@ -86,6 +86,18 @@ async function startMockGitHub(): Promise<{ baseUrl: string; requests: MockReque
|
||||
created_at: "2026-05-20T00:00:00Z",
|
||||
updated_at: "2026-05-20T01:00:00Z",
|
||||
};
|
||||
const shorthandIssue = {
|
||||
id: 7000,
|
||||
number: 7,
|
||||
title: "generic shorthand fixture",
|
||||
body: "HWLAB-style shorthand body fixture\n\nThis is generic CLI coverage, not product data.",
|
||||
state: "open",
|
||||
html_url: "https://github.com/pikasTech/HWLAB/issues/7",
|
||||
comments: 1,
|
||||
user: { login: "tester" },
|
||||
created_at: "2026-05-20T02:00:00Z",
|
||||
updated_at: "2026-05-20T03:00:00Z",
|
||||
};
|
||||
const boardIssueBodyInitial = [
|
||||
"# Code Queue",
|
||||
"",
|
||||
@@ -431,6 +443,10 @@ async function startMockGitHub(): Promise<{ baseUrl: string; requests: MockReque
|
||||
sendJson(res, 200, { ...issue, body: boardIssueBody, updated_at: boardIssueUpdatedAt });
|
||||
return;
|
||||
}
|
||||
if (req.method === "GET" && req.url === "/repos/pikasTech/HWLAB/issues/7") {
|
||||
sendJson(res, 200, shorthandIssue);
|
||||
return;
|
||||
}
|
||||
if (req.method === "GET" && req.url === "/repos/pikasTech/unidesk/issues/24") {
|
||||
sendJson(res, 200, legacyCommanderBriefIssue);
|
||||
return;
|
||||
@@ -455,6 +471,10 @@ async function startMockGitHub(): Promise<{ baseUrl: string; requests: MockReque
|
||||
sendJson(res, 200, comments);
|
||||
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;
|
||||
}
|
||||
if (req.method === "GET" && req.url === "/repos/pikasTech/unidesk/issues/60/comments?per_page=100") {
|
||||
sendJson(res, 200, []);
|
||||
return;
|
||||
@@ -570,6 +590,7 @@ export async function runGhCliIssueGuardContract(): Promise<JsonRecord> {
|
||||
assertCondition(usage.some((line) => line.includes("gh issue list")), "gh help should list issue list", { usage });
|
||||
assertCondition(usage.some((line) => line.includes("gh issue read")), "gh help should list issue read", { usage });
|
||||
assertCondition(usage.some((line) => line.includes("gh issue view")), "gh help should list issue view", { usage });
|
||||
assertCondition(usage.some((line) => line.includes("owner/repo#number") && line.includes("--raw|--full")), "gh help should document issue shorthand and raw/full disclosure", { usage });
|
||||
assertCondition(usage.some((line) => line.includes("gh issue board-row list")), "gh help should list board-row list", { usage });
|
||||
assertCondition(usage.some((line) => line.includes("gh issue board-row update")), "gh help should list board-row update", { usage });
|
||||
assertCondition(usage.some((line) => line.includes("gh issue board-row add")), "gh help should list board-row add", { usage });
|
||||
@@ -578,6 +599,8 @@ export async function runGhCliIssueGuardContract(): Promise<JsonRecord> {
|
||||
assertCondition(usage.some((line) => line.includes("gh issue board-row delete")), "gh help should list board-row delete", { usage });
|
||||
assertCondition(notes.some((line) => line.includes("canonical read path")), "gh help should state issue read is canonical", { notes });
|
||||
assertCondition(notes.some((line) => line.includes("compatibility alias")), "gh help should state issue view is alias", { notes });
|
||||
assertCondition(notes.some((line) => line.includes("owner/repo#number shorthand")), "gh help should explain read/view shorthand", { 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("board-row update changes one table cell")), "gh help should describe board-row update safety", { notes });
|
||||
assertCondition(notes.some((line) => line.includes("board-row upsert updates an existing row")), "gh help should describe board-row upsert safety", { notes });
|
||||
assertCondition(notes.some((line) => line.includes("board-row add/move/delete are row-scoped")), "gh help should describe board-row row mutation safety", { notes });
|
||||
@@ -1113,6 +1136,33 @@ export async function runGhCliIssueGuardContract(): Promise<JsonRecord> {
|
||||
const viewSelectedJson = viewBodyData.json as JsonRecord;
|
||||
assertCondition(typeof viewSelectedJson.body === "string" && viewSelectedJson.body === readIssue.body, "issue view alias should preserve selected json body", viewBodyData);
|
||||
|
||||
const shorthandRaw = await runCli(["gh", "issue", "view", "pikasTech/HWLAB#7", "--raw"], env);
|
||||
assertCondition(shorthandRaw.status === 0, "issue view should accept owner/repo#number shorthand with --raw", shorthandRaw.json ?? { stdout: shorthandRaw.stdout });
|
||||
const shorthandRawData = dataOf(shorthandRaw.json ?? {});
|
||||
assertCondition(shorthandRawData.repo === "pikasTech/HWLAB", "issue shorthand should derive repo from owner/repo#number", shorthandRawData);
|
||||
const shorthandIssueData = shorthandRawData.issue as JsonRecord;
|
||||
assertCondition(shorthandIssueData.number === 7 && String(shorthandIssueData.body ?? "").includes("shorthand body fixture"), "issue shorthand should read the requested issue", shorthandRawData);
|
||||
const shorthandDisclosure = shorthandRawData.disclosure as JsonRecord;
|
||||
assertCondition(shorthandDisclosure.raw === true && shorthandDisclosure.fullDisclosure === true, "--raw should mark explicit full disclosure", shorthandDisclosure);
|
||||
const shorthandSelected = shorthandRawData.json as JsonRecord;
|
||||
assertCondition(shorthandSelected.body === shorthandIssueData.body && Array.isArray(shorthandSelected.comments), "--raw should select the supported full issue read field set", shorthandRawData);
|
||||
assertCondition(mock.requests.some((request) => request.method === "GET" && request.url === "/repos/pikasTech/HWLAB/issues/7"), "issue shorthand should call the derived repo REST path", mock.requests);
|
||||
|
||||
const shorthandConflict = await runCli(["gh", "issue", "read", "pikasTech/HWLAB#7", "--repo", "pikasTech/unidesk", "--raw"], env);
|
||||
assertCondition(shorthandConflict.status !== 0, "issue shorthand with conflicting --repo should fail", shorthandConflict.json ?? { stdout: shorthandConflict.stdout });
|
||||
const shorthandConflictData = failedDataOf(shorthandConflict.json ?? {});
|
||||
assertCondition(shorthandConflictData.degradedReason === "validation-failed", "conflicting --repo should be validation-failed", shorthandConflictData);
|
||||
assertCondition(String(shorthandConflictData.message ?? "").includes("resolves to repo pikasTech/HWLAB"), "conflict message should name the derived repo", shorthandConflictData);
|
||||
const issueConflictCommands = shorthandConflictData.supportedCommands as string[];
|
||||
assertCondition(Array.isArray(issueConflictCommands) && issueConflictCommands.some((command) => command === "bun scripts/cli.ts gh issue read 7 --repo pikasTech/HWLAB --json body,title,state,comments"), "conflict should include the exact supported issue read command", shorthandConflictData);
|
||||
|
||||
const rawUnsupported = await runCli(["gh", "issue", "list", "--raw"], env);
|
||||
assertCondition(rawUnsupported.status !== 0, "--raw outside read/view should fail structurally", rawUnsupported.json ?? { stdout: rawUnsupported.stdout });
|
||||
const rawUnsupportedData = failedDataOf(rawUnsupported.json ?? {});
|
||||
assertCondition(rawUnsupportedData.degradedReason === "validation-failed", "unsupported --raw scope should be validation-failed", rawUnsupportedData);
|
||||
const rawUnsupportedCommands = rawUnsupportedData.supportedCommands as string[];
|
||||
assertCondition(Array.isArray(rawUnsupportedCommands) && rawUnsupportedCommands.some((command) => command.includes("gh issue read owner/name#<number> --raw")), "unsupported raw should suggest read/view raw command shape", rawUnsupportedData);
|
||||
|
||||
const readFields = await runCli(["gh", "issue", "read", "20", "--repo", "pikasTech/unidesk", "--json", "body,title,state,comments"], env);
|
||||
assertCondition(readFields.status === 0, "common --json field selection should succeed", readFields.json ?? { stdout: readFields.stdout });
|
||||
const readFieldsData = dataOf(readFields.json ?? {});
|
||||
@@ -1324,6 +1374,8 @@ export async function runGhCliIssueGuardContract(): Promise<JsonRecord> {
|
||||
checks: [
|
||||
"issue read --json body preserves .data.issue.body",
|
||||
"issue view remains a compatibility alias",
|
||||
"issue read/view accept owner/repo#number shorthand and reject conflicting --repo",
|
||||
"issue read/view --raw is explicit full disclosure",
|
||||
"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",
|
||||
@@ -1341,6 +1393,7 @@ export async function runGhCliIssueGuardContract(): Promise<JsonRecord> {
|
||||
"issue create sends labels through REST and preserves GitHub validation errors for missing labels",
|
||||
"issue list unsupported fields and states fail structurally",
|
||||
"issue read supports body,title,state,comments selection",
|
||||
"unknown/full disclosure option guidance remains actionable",
|
||||
"unsupported --json fields fail structurally",
|
||||
"issue edit --body-file rejects literal null",
|
||||
"#20/#24 body profile guards reject missing headings or wrong profile",
|
||||
|
||||
Reference in New Issue
Block a user