refactor(cli): share parsed output controls across consumers

This commit is contained in:
morluto committed 2026-10-24 00:32:05 +08:00
1 parent 4526862728
commit 8d8d85ba22
13 files changed
+228 -135

No files matched your search

+6 -5
View File
@@ -87,15 +87,16 @@ if (!(await compiledRuntimeExists(runtimeFiles))) {
renderCliOutputArgumentError,
renderEmptyFilteredCliOutput,
renderVersionReport,
validateCliOutputArguments,
parseCliOutputArguments,
} = await import("../dist/cli-runtime/cliOutput.js");
guardClosedStdout();
const cliArgs = normalizeCliArguments(args);
const outputArguments = validateCliOutputArguments(cliArgs);
const outputOptions = parseCliOutputArguments(cliArgs);
const outputArguments = outputOptions.validation;
// `--version` with an explicit format reports the runtime as a document;
// plain `--version` stays Incur's bare version string.
const versionOutput = renderVersionReport(
cliArgs,
outputOptions,
{
packageName: packageJson.name,
packageVersion: packageJson.version,
@@ -129,7 +130,7 @@ if (!(await compiledRuntimeExists(runtimeFiles))) {
const { createCliResultOutput } =
await import("../dist/cli-runtime/cliResultOutput.js");
let wroteOutput = false;
const resultOutput = createCliResultOutput(cliArgs, process.stdout);
const resultOutput = createCliResultOutput(outputOptions, process.stdout);
try {
await createCli(process.env, resultOutput).serve(cliArgs, {
stdout: (output) => {
@@ -182,7 +183,7 @@ if (!(await compiledRuntimeExists(runtimeFiles))) {
process.exitCode = stdoutClosed ? SIGPIPE_EXIT_STATUS : 1;
}
if (!wroteOutput && !resultOutput?.handled)
writeStdout(renderEmptyFilteredCliOutput(cliArgs) ?? "");
writeStdout(renderEmptyFilteredCliOutput(outputOptions) ?? "");
}
} catch (cause) {
dispatchFailure = cause;
+5 -1
View File
@@ -10,6 +10,7 @@ import { finished } from "node:stream/promises";
import { Cli } from "incur";
import { parseCliOutputArguments } from "../../../dist/cliOutput.js";
import { createCliResultOutput } from "../../../dist/cli/cliResultOutput.js";
import { createEvidence } from "../../../dist/domain/evidence.js";
@@ -79,7 +80,10 @@ try {
destination = createWriteStream(path, { flags: "wx", mode: 0o600 });
const argv = [metadata.command, "--format", format, "--full-output"];
const output = createCliResultOutput(argv, destination);
const output = createCliResultOutput(
parseCliOutputArguments(argv),
destination,
);
assert.ok(output);
let unexpectedOutputBytes = 0;
let exitCode = 0;
+3 -8
View File
@@ -4,10 +4,7 @@ import type { Writable } from "node:stream";
import { Filter } from "incur";
import { estimateTokenCount } from "tokenx";
import {
parseCliOutputArguments,
validateCliOutputArguments,
} from "../cliOutput.js";
import type { ParsedCliOutputArguments } from "../cliOutput.js";
import {
cliOperationFailureIdentity,
cliOperationFailurePayload,
@@ -103,12 +100,10 @@ const wrapCommand = (
/** Preserve Incur output controls and stream JSON while framing failures. */
export const createCliResultOutput = (
arguments_: readonly string[],
options: ParsedCliOutputArguments,
destination: Writable,
): CliResultOutput | undefined => {
const options = parseCliOutputArguments(arguments_);
if (options.parseError || !validateCliOutputArguments(arguments_).ok)
return undefined;
if (options.parseError || !options.validation.ok) return undefined;
const format = options.format as "json" | "jsonl" | "yaml" | "toon" | "md";
let handled = false;
let failed = false;
+58 -58
View File
@@ -178,7 +178,7 @@ export type CliOutputArgumentValidation =
readonly message: string;
};
interface ParsedCliOutputArguments {
interface CliOutputControls {
readonly filterOutput: string | undefined;
readonly format: string;
/** Whether `--json` or `--format` selected the format, rather than the default. */
@@ -193,11 +193,19 @@ interface ParsedCliOutputArguments {
readonly tokenOffset: number | undefined;
}
/** Parsed output controls and the decisions shared by every executable consumer. */
export interface ParsedCliOutputArguments extends CliOutputControls {
readonly validation: CliOutputArgumentValidation;
readonly versionReportFormat: CliOutputFormat | undefined;
}
/** Read effective Incur output controls at the executable boundary. */
export const parseCliOutputArguments = (
arguments_: readonly string[],
): ParsedCliOutputArguments => {
let format: string = "toon";
let rootVersion = false;
let versionEligible = true;
let formatExplicit = false;
let filterOutput: string | undefined;
let fullOutput = false;
@@ -205,21 +213,44 @@ export const parseCliOutputArguments = (
let tokenCount = false;
let tokenLimit: number | undefined;
let tokenOffset: number | undefined;
const parsed = (parseError: boolean): ParsedCliOutputArguments => ({
filterOutput,
format,
formatExplicit,
fullOutput,
missingValue,
parseError,
tokenCount,
tokenWindow:
!parseError && (tokenLimit !== undefined || tokenOffset !== undefined),
tokenLimit: parseError ? undefined : tokenLimit,
tokenOffset: parseError ? undefined : tokenOffset,
});
const parsed = (parseError: boolean): ParsedCliOutputArguments => {
const controls: CliOutputControls = {
filterOutput,
format,
formatExplicit,
fullOutput,
missingValue,
parseError,
tokenCount,
tokenWindow:
!parseError && (tokenLimit !== undefined || tokenOffset !== undefined),
tokenLimit: parseError ? undefined : tokenLimit,
tokenOffset: parseError ? undefined : tokenOffset,
};
return {
...controls,
validation: validateCliOutputControls(controls),
versionReportFormat:
rootVersion &&
versionEligible &&
formatExplicit &&
!parseError &&
missingValue === undefined &&
isCliOutputFormat(format)
? format
: undefined,
};
};
for (let index = 0; index < arguments_.length; index += 1) {
const argument = arguments_[index];
// Only these flags can accompany a formatted root version request.
// Valued output flags consume their data below before the next iteration.
if (argument === "--version") {
const next = arguments_[index + 1];
if (next === undefined || next.startsWith("-")) rootVersion = true;
else versionEligible = false;
} else if (argument !== "--json" && argument !== "--format")
versionEligible = false;
// Incur scans builtins across `--` and reads spaced value forms, which
// normalizeCliArguments has produced. Mirror those effective values.
if (argument === "--json") {
@@ -288,11 +319,13 @@ export const missingCliOutputValueMessage = (flag: string): string =>
`${flag} requires a value, as in ${flag} <value> or ${flag}=<value>.`;
/** Reject output controls that cannot safely produce a complete result. */
export const validateCliOutputArguments = (
arguments_: readonly string[],
): CliOutputArgumentValidation => {
const { filterOutput, format, missingValue, parseError, tokenWindow } =
parseCliOutputArguments(arguments_);
const validateCliOutputControls = ({
filterOutput,
format,
missingValue,
parseError,
tokenWindow,
}: CliOutputControls): CliOutputArgumentValidation => {
if (parseError) return { ok: true };
if (filterOutput !== undefined && hasUnclosedFilterSlice(filterOutput))
return {
@@ -325,11 +358,11 @@ export const validateCliOutputArguments = (
};
/** Preserve a complete document when Incur filters the entire result away. */
export const renderEmptyFilteredCliOutput = (
arguments_: readonly string[],
): string | undefined => {
const { filterOutput, format, parseError } =
parseCliOutputArguments(arguments_);
export const renderEmptyFilteredCliOutput = ({
filterOutput,
format,
parseError,
}: ParsedCliOutputArguments): string | undefined => {
if (parseError) return undefined;
if (
filterOutput &&
@@ -352,48 +385,15 @@ export const renderCliOutputArgumentError = (
return `${JSON.stringify(value)}\n`;
};
/**
* The format of a root `--version` request that selects one explicitly, or
* undefined when Incur should answer it. Only `--version`, `--json` and
* `--format <format>` may appear, so help keeps precedence, a command keeps
* its own `--version` option, and other output controls keep Incur's plain
* version. Plain `--version` stays the bare version string.
*/
export const versionReportFormat = (
arguments_: readonly string[],
): CliOutputFormat | undefined => {
let version = false;
for (let index = 0; index < arguments_.length; index += 1) {
const argument = arguments_[index];
if (argument === "--version") {
const next = arguments_[index + 1];
// A following value belongs to a command-local `--version` option.
if (next !== undefined && !next.startsWith("-")) return undefined;
version = true;
} else if (argument === "--format") index += 1;
else if (argument !== "--json") return undefined;
}
if (!version) return undefined;
const { format, formatExplicit, missingValue, parseError } =
parseCliOutputArguments(arguments_);
return formatExplicit &&
!parseError &&
missingValue === undefined &&
isCliOutputFormat(format)
? format
: undefined;
};
/**
* Render the version report for a formatted root `--version` request, or
* return undefined so Incur prints the bare version or handles the request.
*/
export const renderVersionReport = (
arguments_: readonly string[],
{ versionReportFormat: format }: ParsedCliOutputArguments,
host: VersionReportHost,
stdoutIsTerminal: boolean,
): string | undefined => {
const format = versionReportFormat(arguments_);
return format === undefined
? undefined
: renderCliDocument(versionReport(host), format, stdoutIsTerminal);
+47 -26
View File
@@ -12,8 +12,7 @@ import {
renderCliDocument,
renderEmptyFilteredCliOutput,
renderVersionReport,
validateCliOutputArguments,
versionReportFormat,
parseCliOutputArguments,
} from "../../../src/cliOutput.js";
const CLI_INTEGRATION_TIMEOUT_MS = 60_000;
@@ -29,13 +28,13 @@ describe("CLI output argument boundary", () => {
it("rejects token windows that would corrupt structured output", () => {
for (const format of ["json", "jsonl", "yaml"] as const) {
const validation = validateCliOutputArguments([
const validation = parseCliOutputArguments([
"providers",
"--token-limit",
"5",
"--format",
format,
]);
]).validation;
expect(validation).toMatchObject({
ok: false,
format,
@@ -56,16 +55,17 @@ describe("CLI output argument boundary", () => {
}
}
expect(
validateCliOutputArguments([
parseCliOutputArguments([
"providers",
"--token-limit",
"5",
"--format",
"toon",
]),
]).validation,
).toEqual({ ok: true });
expect(
validateCliOutputArguments(["providers", "--token-count", "--json"]),
parseCliOutputArguments(["providers", "--token-count", "--json"])
.validation,
).toEqual({ ok: true });
});
});
@@ -129,15 +129,16 @@ describe("CLI output flag value forms", () => {
it("reports a missing value only for a flag the parser did not consume", () => {
expect(
validateCliOutputArguments([
parseCliOutputArguments([
"capabilities",
"--json",
"--filter-output",
"--format",
]),
]).validation,
).toEqual({ ok: true });
expect(
validateCliOutputArguments(["capabilities", "--json", "--format"]),
parseCliOutputArguments(["capabilities", "--json", "--format"])
.validation,
).toMatchObject({ ok: false, code: "MISSING_OUTPUT_VALUE" });
});
});
@@ -151,12 +152,12 @@ describe("unclosed CLI output filter slices", () => {
"providers],name[0,1]",
])("rejects %s before the filter parser runs", (filter) => {
expect(
validateCliOutputArguments([
parseCliOutputArguments([
"providers",
"--json",
"--filter-output",
filter,
]),
]).validation,
).toMatchObject({ ok: false, code: "INVALID_OUTPUT_FILTER" });
});
@@ -173,7 +174,9 @@ describe("unclosed CLI output filter slices", () => {
["--format", "invalid", "--filter-output", "providers["],
["--token-limit", "invalid", "--filter-output", "providers["],
])
expect(validateCliOutputArguments(arguments_)).toEqual({ ok: true });
expect(parseCliOutputArguments(arguments_).validation).toEqual({
ok: true,
});
});
workspaceCliTest(
@@ -356,19 +359,25 @@ describe("CLI output argument boundary", () => {
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",
]),
renderEmptyFilteredCliOutput(
parseCliOutputArguments([
"providers",
"--format",
format,
"--filter-output",
"missing",
]),
),
).toBe("{}\n");
expect(
renderEmptyFilteredCliOutput(["providers", "--filter-output", "missing"]),
renderEmptyFilteredCliOutput(
parseCliOutputArguments(["providers", "--filter-output", "missing"]),
),
).toBeUndefined();
expect(
renderEmptyFilteredCliOutput(["providers", "--format", "json"]),
renderEmptyFilteredCliOutput(
parseCliOutputArguments(["providers", "--format", "json"]),
),
).toBeUndefined();
});
});
@@ -626,7 +635,9 @@ describe("version report requests", () => {
[["--version", "--format"], undefined],
[["--json"], undefined],
] as const)("reads %j as format %s", (arguments_, expected) => {
expect(versionReportFormat(arguments_)).toBe(expected);
expect(parseCliOutputArguments(arguments_).versionReportFormat).toBe(
expected,
);
});
});
@@ -640,8 +651,16 @@ describe("version report rendering", () => {
};
it("indents JSON for a terminal and compacts it when piped", () => {
const terminal = renderVersionReport(["--version", "--json"], host, true);
const piped = renderVersionReport(["--version", "--json"], host, false);
const terminal = renderVersionReport(
parseCliOutputArguments(["--version", "--json"]),
host,
true,
);
const piped = renderVersionReport(
parseCliOutputArguments(["--version", "--json"]),
host,
false,
);
expect(terminal?.split("\n").length).toBeGreaterThan(2);
expect(piped?.trimEnd().split("\n")).toHaveLength(1);
@@ -649,6 +668,8 @@ describe("version report rendering", () => {
});
it("leaves plain --version to Incur", () => {
expect(renderVersionReport(["--version"], host, true)).toBeUndefined();
expect(
renderVersionReport(parseCliOutputArguments(["--version"]), host, true),
).toBeUndefined();
});
});
@@ -4,6 +4,7 @@ import { Cli, Formatter } from "incur";
import { estimateTokenCount } from "tokenx";
import { describe, expect, it } from "vitest";
import { parseCliOutputArguments } from "../../../src/cliOutput.js";
import {
createCliResultOutput,
streamCliCommandResults,
@@ -34,7 +35,10 @@ const renderWindow = async (
"--token-offset",
String(offset),
];
const output = createCliResultOutput(argv, destination);
const output = createCliResultOutput(
parseCliOutputArguments(argv),
destination,
);
if (output === undefined) throw new Error("Missing TOON output surface");
const handled = await output.write(value, {
command: "fixture",
@@ -191,7 +195,12 @@ describe("token windows through Incur output policies", () => {
},
});
const output = createCliResultOutput(
["fixture", "--filter-output", "missing", ...windowFlags],
parseCliOutputArguments([
"fixture",
"--filter-output",
"missing",
...windowFlags,
]),
destination,
)!;
const handled = await output.write(
@@ -249,7 +258,10 @@ describe("token windows through Incur output policies", () => {
callback();
},
});
const output = createCliResultOutput(argv, destination)!;
const output = createCliResultOutput(
parseCliOutputArguments(argv),
destination,
)!;
const cli = Cli.create("policy-test", { sync: false });
if (grouped) {
const group = Cli.create("group", {
+1 -1
View File
@@ -8,7 +8,7 @@ import { workspaceCliTest } from "../../support/cli/workspaceCliFixture.js";
import type { TestWorkspace } from "../../support/workspace/workspaceFixture.js";
const CLI_OUTPUT_RUNTIME =
"export const cliConfigurationError = () => undefined; export const normalizeCliArguments = (args) => args; export const validateCliOutputArguments = () => ({ ok: true }); export const renderCliOutputArgumentError = () => ''; export const renderEmptyFilteredCliOutput = () => undefined; export const renderVersionReport = () => undefined;\n";
"export const cliConfigurationError = () => undefined; export const normalizeCliArguments = (args) => args; export const parseCliOutputArguments = () => ({ validation: { ok: true } }); export const renderCliOutputArgumentError = () => ''; export const renderEmptyFilteredCliOutput = () => undefined; export const renderVersionReport = () => undefined;\n";
const installCliRuntime = async (
workspace: TestWorkspace,
+11 -5
View File
@@ -4,6 +4,7 @@ import { Cli, Formatter } from "incur";
import { estimateTokenCount } from "tokenx";
import { expect, it } from "vitest";
import { parseCliOutputArguments } from "../../../src/cliOutput.js";
import {
createCliResultOutput,
streamCliCommandResults,
@@ -28,7 +29,7 @@ const runFilteredCli = async (
const args = ["rows", "--format", format, "--filter-output", filter];
const chunks: Buffer[] = [];
const output = createCliResultOutput(
args,
parseCliOutputArguments(args),
new Writable({
write(chunk: Buffer, _encoding, callback) {
chunks.push(chunk);
@@ -75,7 +76,7 @@ it.each(
];
const chunks: Buffer[] = [];
const output = createCliResultOutput(
args,
parseCliOutputArguments(args),
new Writable({
write(chunk: Buffer, _encoding, callback) {
chunks.push(chunk);
@@ -128,7 +129,7 @@ it.each(["json", "jsonl"] as const)(
];
const chunks: Buffer[] = [];
const output = createCliResultOutput(
args,
parseCliOutputArguments(args),
new Writable({
write(chunk: Buffer, _encoding, callback) {
chunks.push(chunk);
@@ -161,7 +162,7 @@ it.each(["selected", "toString", "constructor"])(
const before = structuredClone(value);
const chunks: Buffer[] = [];
const output = createCliResultOutput(
["--json", "--filter-output", filter],
parseCliOutputArguments(["--json", "--filter-output", filter]),
new Writable({
write(chunk: Buffer, _encoding, callback) {
chunks.push(chunk);
@@ -187,7 +188,12 @@ it("finishes JSONL after a bounded large record before a missing selection", asy
const value = [{ selected: "x".repeat(80 * 1024) }, {}];
const chunks: Buffer[] = [];
const output = createCliResultOutput(
["--format", "jsonl", "--filter-output", "selected"],
parseCliOutputArguments([
"--format",
"jsonl",
"--filter-output",
"selected",
]),
new Writable({
highWaterMark: 1,
write(chunk: Buffer, _encoding, callback) {
@@ -5,6 +5,7 @@ import { Cli, z } from "incur";
import { parse as parseYaml } from "yaml";
import { expect, it } from "vitest";
import { parseCliOutputArguments } from "../../../src/cliOutput.js";
import {
createCliResultOutput,
streamCliCommandResults,
@@ -21,7 +22,10 @@ const runCli = async (arguments_: readonly string[]) => {
callback();
},
});
const output = createCliResultOutput(arguments_, destination);
const output = createCliResultOutput(
parseCliOutputArguments(arguments_),
destination,
);
if (output === undefined) throw new Error("Missing CLI result output owner");
const cli = Cli.create("filtered-failure-test", { sync: false });
cli.command("trace-application-feature", {
@@ -178,7 +182,10 @@ it("keeps successful scalar filtering available", async () => {
},
});
const argv = ["open-control", "--json", "--filter-output", "open"];
const output = createCliResultOutput(argv, destination);
const output = createCliResultOutput(
parseCliOutputArguments(argv),
destination,
);
if (output === undefined) throw new Error("Missing CLI result output owner");
const cli = Cli.create("filtered-failure-test", { sync: false });
cli.command("open-control", { run: () => ({ open: false }) });
+17 -7
View File
@@ -3,6 +3,7 @@ import { Writable } from "node:stream";
import { Cli } from "incur";
import { describe, expect, it } from "vitest";
import { parseCliOutputArguments } from "../../../src/cliOutput.js";
import {
createCliResultOutput,
streamCliCommandResults,
@@ -29,7 +30,10 @@ describe("CLI formatting stage admission", () => {
{ cta: { commands: [{ command: "next" }] } },
),
});
const output = createCliResultOutput(["fixture"], new Writable());
const output = createCliResultOutput(
parseCliOutputArguments(["fixture"]),
new Writable(),
);
if (output === undefined) throw new Error("Missing output owner");
if (wrapped) streamCliCommandResults(cli, output);
let reply = "";
@@ -75,7 +79,10 @@ describe("CLI formatting stage admission", () => {
});
it("declines errors before a result or from a different failure class", async () => {
const output = createCliResultOutput([], new Writable());
const output = createCliResultOutput(
parseCliOutputArguments([]),
new Writable(),
);
expect(output).toBeDefined();
expect(
await output?.recoverFormattingFailure(
@@ -99,7 +106,10 @@ describe("CLI formatting stage admission", () => {
throw new RangeError("Invalid string length");
},
});
const output = createCliResultOutput([], new Writable());
const output = createCliResultOutput(
parseCliOutputArguments([]),
new Writable(),
);
if (output === undefined) throw new Error("Missing output owner");
streamCliCommandResults(cli, output);
let text = "";
@@ -124,7 +134,7 @@ describe("CLI formatting recovery outcomes", () => {
const cause = new RangeError("Invalid string length");
const chunks: Buffer[] = [];
const output = createCliResultOutput(
["--format", "md"],
parseCliOutputArguments(["--format", "md"]),
new Writable({
write(chunk: Buffer, _encoding, callback) {
chunks.push(chunk);
@@ -154,7 +164,7 @@ describe("CLI formatting recovery outcomes", () => {
it("retains ownership if the recovery document cannot be written", async () => {
const cause = new RangeError("Invalid string length");
const output = createCliResultOutput(
[],
parseCliOutputArguments([]),
new Writable({
write(_chunk, _encoding, callback) {
callback(cause);
@@ -171,7 +181,7 @@ describe("CLI formatting recovery outcomes", () => {
it("does not replace a failed destination, even for the same RangeError", async () => {
const cause = new RangeError("Invalid string length");
const output = createCliResultOutput(
["--json"],
parseCliOutputArguments(["--json"]),
new Writable({
write(_chunk, _encoding, callback) {
callback(cause);
@@ -189,7 +199,7 @@ describe("CLI formatting recovery outcomes", () => {
it("retains the original failure identity and ignores large details on recovery", async () => {
const chunks: Buffer[] = [];
const output = createCliResultOutput(
[],
parseCliOutputArguments([]),
new Writable({
write(chunk: Buffer, _encoding, callback) {
chunks.push(chunk);
+43 -11
View File
@@ -8,6 +8,7 @@ import { parse as parseYaml } from "yaml";
import { describe, expect, it } from "vitest";
import { writeJsonOutput } from "../../../src/cli/jsonOutput.js";
import { parseCliOutputArguments } from "../../../src/cliOutput.js";
import {
createCliResultOutput,
streamCliCommandResults,
@@ -171,7 +172,7 @@ describe("CLI command output wrapping", () => {
},
});
const output = createCliResultOutput(
["--format", "json", "--full-output"],
parseCliOutputArguments(["--format", "json", "--full-output"]),
destination,
);
if (output === undefined)
@@ -234,7 +235,10 @@ describe("CLI result output surface", () => {
"--filter-output",
"message",
];
const output = createCliResultOutput(argv, destination);
const output = createCliResultOutput(
parseCliOutputArguments(argv),
destination,
);
if (output === undefined)
throw new Error("Missing structured output surface");
const cli = Cli.create("rea-output-test", { sync: false });
@@ -311,7 +315,10 @@ describe("CLI result output surface", () => {
},
});
const argv = ["fixture", "--json", "--full-output"];
const output = createCliResultOutput(argv, destination);
const output = createCliResultOutput(
parseCliOutputArguments(argv),
destination,
);
if (output === undefined) throw new Error("Missing JSON output surface");
const cli = Cli.create("rea-output-test", { sync: false });
cli.command("fixture", {
@@ -356,7 +363,10 @@ describe("full-output token windows", () => {
"--token-limit",
"3",
];
const output = createCliResultOutput(argv, destination);
const output = createCliResultOutput(
parseCliOutputArguments(argv),
destination,
);
if (output === undefined) throw new Error("Missing TOON output surface");
const cli = Cli.create("rea-output-test", { sync: false });
registerManagedCommands(cli, silentLogger, async () => ({
@@ -402,7 +412,10 @@ describe("logger classification handoff", () => {
source: "captured-metadata",
};
const argv = ["fixture", "--json", "--full-output"];
const output = createCliResultOutput(argv, destination);
const output = createCliResultOutput(
parseCliOutputArguments(argv),
destination,
);
if (output === undefined) throw new Error("Missing JSON output surface");
const cli = Cli.create("rea-output-test", { sync: false });
cli.command("fixture", {
@@ -455,7 +468,10 @@ describe("full-output token counts", () => {
"--filter-output",
"message",
];
const output = createCliResultOutput(argv, destination);
const output = createCliResultOutput(
parseCliOutputArguments(argv),
destination,
);
if (output === undefined) throw new Error("Missing JSON output surface");
const cli = Cli.create("rea-output-test", { sync: false });
registerManagedCommands(cli, silentLogger, async () => failure);
@@ -479,7 +495,12 @@ describe("CLI result output surface", () => {
["--json", "--token-limit", "5"],
["--json", "--format", "invalid"],
])("delegates unsupported output controls %j", (...arguments_) => {
expect(createCliResultOutput(arguments_, new Writable())).toBe(undefined);
expect(
createCliResultOutput(
parseCliOutputArguments(arguments_),
new Writable(),
),
).toBe(undefined);
});
it("keeps ownership after a failed write so no second document is emitted", async () => {
@@ -489,7 +510,10 @@ describe("CLI result output surface", () => {
callback(cause);
},
});
const output = createCliResultOutput(["--json"], destination);
const output = createCliResultOutput(
parseCliOutputArguments(["--json"]),
destination,
);
if (output === undefined) throw new Error("Missing JSON output surface");
await expect(
output.write(
@@ -541,7 +565,10 @@ describe("CLI result output surface", () => {
// A terminal destination exercises the indented encoding past the limit.
Object.defineProperty(destination, "isTTY", { value: true });
const argv = ["inspect-managed-members", "fixture.dll", "--json"];
const output = createCliResultOutput(argv, destination);
const output = createCliResultOutput(
parseCliOutputArguments(argv),
destination,
);
if (output === undefined) throw new Error("Missing JSON output surface");
streamCliCommandResults(cli, output);
let exitCode = 0;
@@ -562,7 +589,7 @@ describe("CLI result output surface", () => {
const chunks: Buffer[] = [];
const tokenArgv = [...argv, "--token-count", "--full-output"];
const tokenOutput = createCliResultOutput(
tokenArgv,
parseCliOutputArguments(tokenArgv),
new Writable({
write(chunk: Buffer, _encoding, callback) {
chunks.push(chunk);
@@ -623,7 +650,12 @@ describe("streamed CLI filtered selections", () => {
},
});
const output = createCliResultOutput(
["--format", format, "--filter-output", filter],
parseCliOutputArguments([
"--format",
format,
"--filter-output",
filter,
]),
destination,
);
if (output === undefined) throw new Error("Missing JSON output surface");
@@ -59,7 +59,7 @@ const {
normalizeCliArguments,
renderCliOutputArgumentError,
renderEmptyFilteredCliOutput,
validateCliOutputArguments,
parseCliOutputArguments,
} = await import("../../../dist/cli-runtime/cliOutput.js");
const writeFrame = (value) =>
@@ -162,7 +162,8 @@ async function runOneShot(args) {
let explicitExit;
try {
const cliArgs = normalizeCliArguments(args);
const outputArguments = validateCliOutputArguments(cliArgs);
const outputOptions = parseCliOutputArguments(cliArgs);
const outputArguments = outputOptions.validation;
const configurationError = cliConfigurationError(process.env);
if (configurationError !== undefined && !isDoctorCommand(cliArgs)) {
process.stderr.write(`${configurationError}\n`);
@@ -172,7 +173,7 @@ async function runOneShot(args) {
process.exitCode = 1;
} else {
let wroteOutput = false;
const resultOutput = createCliResultOutput(cliArgs, destination);
const resultOutput = createCliResultOutput(outputOptions, destination);
try {
await createCli(process.env, resultOutput).serve(cliArgs, {
stdout: (output) => {
@@ -194,7 +195,7 @@ async function runOneShot(args) {
process.exitCode = 1;
}
if (!wroteOutput && !resultOutput?.handled) {
const empty = renderEmptyFilteredCliOutput(cliArgs);
const empty = renderEmptyFilteredCliOutput(outputOptions);
if (empty) process.stdout.write(empty);
}
}
+8 -4
View File
@@ -10,7 +10,7 @@ const {
normalizeCliArguments,
renderCliOutputArgumentError,
renderEmptyFilteredCliOutput,
validateCliOutputArguments,
parseCliOutputArguments,
} = await import("../../../dist/cli-runtime/cliOutput.js");
const MAX_STDOUT_CHARS = 16 * 1024 * 1024;
@@ -38,7 +38,10 @@ const destination = new Writable({
},
});
Object.defineProperty(destination, "isTTY", { value: false });
const resultOutput = createCliResultOutput(["--json"], destination);
const resultOutput = createCliResultOutput(
parseCliOutputArguments(["--json"]),
destination,
);
const cli = createCli(process.env, resultOutput);
const resetOutput = () => {
@@ -52,7 +55,8 @@ const runCommand = async (args) => {
resetOutput();
try {
const cliArgs = normalizeCliArguments(args);
const outputArguments = validateCliOutputArguments(cliArgs);
const outputOptions = parseCliOutputArguments(cliArgs);
const outputArguments = outputOptions.validation;
if (!outputArguments.ok)
return {
exitCode: 1,
@@ -87,7 +91,7 @@ const runCommand = async (args) => {
};
}
if (!wroteOutput && !resultOutput?.handled) {
const empty = renderEmptyFilteredCliOutput(cliArgs);
const empty = renderEmptyFilteredCliOutput(outputOptions);
if (empty !== undefined) destination.write(empty);
}
// logCliCommand reports some failures only through process.exitCode.