mirror of
https://github.com/morluto/rea
synced 2026-10-26 06:16:17 -04:00
fix(cli): emit empty structured filter projections (#914)
This commit is contained in:
+9
-1
@@ -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
@@ -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 }>,
|
||||
|
||||
@@ -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 }) => {
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user