diff --git a/scripts/src/cli-input-errors.test.ts b/scripts/src/cli-input-errors.test.ts index 0ce1b70e..6c57b562 100644 --- a/scripts/src/cli-input-errors.test.ts +++ b/scripts/src/cli-input-errors.test.ts @@ -15,7 +15,12 @@ describe("CLI input error hierarchy", () => { }); test("explicit machine output returns a JSON input-error envelope without a stack", () => { - for (const args of [["definitely-not-a-command", "--json"], ["definitely-not-a-command", "-o", "json"]]) { + for (const args of [ + ["definitely-not-a-command", "--json"], + ["definitely-not-a-command", "--full"], + ["definitely-not-a-command", "--raw"], + ["definitely-not-a-command", "-o", "json"], + ]) { const result = runCli(...args); expect(result.status).toBe(1); expect(result.stderr).toBe(""); @@ -30,6 +35,11 @@ describe("CLI input error hierarchy", () => { expect(payload.error.stack).toBeUndefined(); expect(result.stdout).not.toContain("stackOmitted"); } + + const nonJsonOutput = runCli("definitely-not-a-command", "-o", "yaml"); + expect(nonJsonOutput.status).toBe(1); + expect(nonJsonOutput.stdout).toContain("ERROR cli-input/unknown-command"); + expect(() => JSON.parse(nonJsonOutput.stdout)).toThrow(); }); test("predictable Gitea option validation is compact and stack-free", () => { @@ -43,6 +53,24 @@ describe("CLI input error hierarchy", () => { expect(result.stdout).not.toContain("platform-infra-gitea.ts:"); }); + test("Gitea full and raw input errors preserve machine JSON without a stack", () => { + for (const outputFlag of ["--full", "--raw"]) { + const result = runCli("platform-infra", "gitea", "status", "--target", "NC01", outputFlag, "--definitely-unsupported"); + expect(result.status).toBe(1); + expect(result.stderr).toBe(""); + const payload = JSON.parse(result.stdout) as { + ok: boolean; + error: { kind: string; code: string; argument: string; debug: boolean; stack?: string }; + }; + expect(payload.ok).toBe(false); + expect(payload.error.kind).toBe("cli-input"); + expect(payload.error.code).toBe("unsupported-option"); + expect(payload.error.argument).toBe("--definitely-unsupported"); + expect(payload.error.debug).toBe(false); + expect(payload.error.stack).toBeUndefined(); + } + }); + test("only an explicitly unknown command target is converted to a compact input error", () => { const unknownTarget = runCli("platform-infra", "gitea", "status", "--target", "definitely-nope"); expect(unknownTarget.status).toBe(1); diff --git a/scripts/src/output.ts b/scripts/src/output.ts index c4d2787e..70765539 100644 --- a/scripts/src/output.ts +++ b/scripts/src/output.ts @@ -97,7 +97,7 @@ export function emitText(text: string, command = "text"): void { export function emitError(command: string, error: unknown): void { const safeCommand = redactSensitiveCommandArgs(command); - if (error instanceof CliInputError && !cliDebugEnabled() && !cliJsonOutputRequested()) { + if (error instanceof CliInputError && !cliDebugEnabled() && !cliMachineOutputRequested()) { safeStdoutWrite(renderTextOutput(safeCommand, renderCliInputErrorText(safeCommand, error))); return; } @@ -175,9 +175,9 @@ function cliInputErrorPayload(error: CliInputError): Record { }; } -function cliJsonOutputRequested(argv = process.argv.slice(2)): boolean { +function cliMachineOutputRequested(argv = process.argv.slice(2)): boolean { for (let index = 0; index < argv.length; index += 1) { - if (argv[index] === "--json") return true; + if (["--json", "--full", "--raw"].includes(argv[index] ?? "")) return true; if (argv[index] === "-o" && argv[index + 1] === "json") return true; } return false;