fix(cli): emit empty structured filter projections (#914)

This commit is contained in:
Kaoru0822
2026-10-07 02:51:10 -07:00
committed by GitHub
parent 7a9f587c3a
commit 2db668f6ca
4 changed files with 126 additions and 7 deletions
+9 -1
View File
@@ -41,6 +41,7 @@ if (!(await compiledRuntimeExists(runtimeFiles))) {
const { createCli } = await import("../dist/cli.js");
const {
renderCliOutputArgumentError,
renderEmptyFilteredCliOutput,
sanitizeCliOutput,
validateCliOutputArguments,
} = await import("../dist/cliOutput.js");
@@ -49,9 +50,16 @@ if (!(await compiledRuntimeExists(runtimeFiles))) {
process.stdout.write(renderCliOutputArgumentError(outputArguments));
process.exitCode = 1;
} else {
let wroteOutput = false;
await createCli().serve(args, {
stdout: (output) => process.stdout.write(sanitizeCliOutput(output)),
stdout: (output) => {
const sanitized = sanitizeCliOutput(output);
if (sanitized.length > 0) wroteOutput = true;
process.stdout.write(sanitized);
},
});
if (!wroteOutput)
process.stdout.write(renderEmptyFilteredCliOutput(args) ?? "");
}
}
+39 -5
View File
@@ -18,6 +18,12 @@ export type CliOutputArgumentValidation =
type JsonRecord = Record<string, unknown>;
interface ParsedCliOutputArguments {
readonly filterOutput: boolean;
readonly format: string;
readonly tokenWindow: boolean;
}
const isRecord = (value: unknown): value is JsonRecord =>
typeof value === "object" && value !== null && !Array.isArray(value);
@@ -28,16 +34,16 @@ const validationError = (value: JsonRecord): JsonRecord | undefined => {
return undefined;
};
/** Reject text-window controls that would corrupt a structured document. */
export const validateCliOutputArguments = (
const parseCliOutputArguments = (
arguments_: readonly string[],
): CliOutputArgumentValidation => {
): ParsedCliOutputArguments => {
let format: string = "toon";
let filterOutput = false;
let tokenWindow = false;
for (let index = 0; index < arguments_.length; index += 1) {
const argument = arguments_[index];
// Incur scans builtins across `--` and accepts only the spaced --format form.
// Mirror its effective format so malformed requests still emit complete JSON.
// Incur scans builtins across `--` and accepts only spaced value forms.
// Mirror those effective values at the executable output boundary.
if (argument === "--json") {
format = "json";
continue;
@@ -50,6 +56,13 @@ export const validateCliOutputArguments = (
}
continue;
}
if (argument === "--filter-output") {
if (arguments_[index + 1] !== undefined) {
filterOutput = true;
index += 1;
}
continue;
}
if (argument === "--token-limit" || argument === "--token-offset") {
if (arguments_[index + 1] !== undefined) {
tokenWindow = true;
@@ -63,6 +76,14 @@ export const validateCliOutputArguments = (
)
tokenWindow = true;
}
return { filterOutput, format, tokenWindow };
};
/** Reject text-window controls that would corrupt a structured document. */
export const validateCliOutputArguments = (
arguments_: readonly string[],
): CliOutputArgumentValidation => {
const { format, tokenWindow } = parseCliOutputArguments(arguments_);
if (
tokenWindow &&
(format === "json" || format === "jsonl" || format === "yaml")
@@ -76,6 +97,19 @@ export const validateCliOutputArguments = (
return { ok: true };
};
/** Preserve a complete document when Incur filters the entire result away. */
export const renderEmptyFilteredCliOutput = (
arguments_: readonly string[],
): string | undefined => {
const { filterOutput, format } = parseCliOutputArguments(arguments_);
if (
filterOutput &&
(format === "json" || format === "jsonl" || format === "yaml")
)
return "{}\n";
return undefined;
};
/** Render one complete structured error before command execution. */
export const renderCliOutputArgumentError = (
error: Exclude<CliOutputArgumentValidation, { readonly ok: true }>,
+77
View File
@@ -8,6 +8,7 @@ import { workspaceCliTest } from "../../support/cli/workspaceCliFixture.js";
import {
renderCliOutputArgumentError,
renderEmptyFilteredCliOutput,
sanitizeCliOutput,
validateCliOutputArguments,
} from "../../../src/cliOutput.js";
@@ -140,9 +141,85 @@ describe("CLI output argument and sanitization boundary", () => {
meta: { command: "analyze" },
});
});
it("renders an explicit empty projection for structured filtered output", () => {
for (const format of ["json", "jsonl", "yaml"])
expect(
renderEmptyFilteredCliOutput([
"providers",
"--format",
format,
"--filter-output",
"missing",
]),
).toBe("{}\n");
expect(
renderEmptyFilteredCliOutput(["providers", "--filter-output", "missing"]),
).toBeUndefined();
expect(
renderEmptyFilteredCliOutput(["providers", "--format", "json"]),
).toBeUndefined();
});
});
describe("compiled CLI output boundary", () => {
workspaceCliTest(
"emits valid JSON when an output filter misses the top-level result",
async ({ cli }) => {
const result = await cli.run({
arguments: [
"analyze-javascript-application",
"tests/conformance/readiness/javascript-cli",
"--format",
"json",
"--filter-output",
"summary",
],
});
expect(result).toMatchObject({
exitCode: 0,
stdout: "{}\n",
stderr: "",
json: {},
});
const nested = await cli.run({
arguments: [
"analyze-javascript-application",
"tests/conformance/readiness/javascript-cli",
"--format",
"json",
"--filter-output",
"normalized_result.not_a_field",
],
});
expect(nested).toMatchObject({
exitCode: 0,
stderr: "",
json: { normalized_result: {} },
});
const selected = await cli.run({
arguments: [
"analyze-javascript-application",
"tests/conformance/readiness/javascript-cli",
"--format",
"json",
"--filter-output",
"normalized_result.summary",
],
});
expect(selected).toMatchObject({
exitCode: 0,
stderr: "",
json: {
normalized_result: {
summary: expect.objectContaining({ browser_windows: 0 }),
},
},
});
},
CLI_INTEGRATION_TIMEOUT_MS,
);
workspaceCliTest(
"sanitizes a real missing-argument dispatcher failure",
async ({ cli }) => {
+1 -1
View File
@@ -27,7 +27,7 @@ describe("executable dispatcher", () => {
),
workspace.write(
"dist/cliOutput.js",
"export const sanitizeCliOutput = (output) => output; export const validateCliOutputArguments = () => ({ ok: true }); export const renderCliOutputArgumentError = () => '';\n",
"export const sanitizeCliOutput = (output) => output; export const validateCliOutputArguments = () => ({ ok: true }); export const renderCliOutputArgumentError = () => ''; export const renderEmptyFilteredCliOutput = () => undefined;\n",
),
workspace.write(
"dist/mcpDoctor.js",