Files
rea/tests/support/cli/compiledCliWorker.mjs
T
kiran 5d2c8c2b78 feat(cli)!: export row-oriented results as JSON Lines (#5629)
* feat(cli): add JSON Lines output for row-oriented results (#5487)

- Support opt-in JSON Lines format (--format jsonl) for row-oriented results and collection views with clear record boundaries
- Preserve stable source identities and stream records deterministically line-by-line
- Emit final coverage and limitation metadata as a distinct line with https://rea.dev/schemas/jsonl-coverage-v1.json to prevent confusion with data rows
- Distinguish error outputs with https://rea.dev/schemas/jsonl-error-v1.json
- Reject non-row-oriented results that lack record boundaries with an unreadable_output error
- Document JSON Lines streaming conventions in docs/cli.md
- Add boundary tests covering empty results, multiline strings, partial coverage, large fields, and non-row rejection

* fix(cli): preserve diagnostics, provenance, and nested collections in JSONL

* fix(cli): frame classified failures and reject unrepresented JSONL facets

---------

Co-authored-by: gensokyo-dev <342580529+gensokyo-dev@users.noreply.github.com>
2026-10-24 02:13:28 +08:00

152 lines
4.4 KiB
JavaScript

import { Writable } from "node:stream";
import { createInterface } from "node:readline";
const { closeCliJavaScriptResources, createCli } =
await import("../../../dist/cli-runtime/cli.js");
const { createCliResultOutput } =
await import("../../../dist/cli-runtime/cliResultOutput.js");
const {
cliConfigurationError,
normalizeCliArguments,
renderCliOutputArgumentError,
renderEmptyFilteredCliOutput,
parseCliOutputArguments,
} = await import("../../../dist/cli-runtime/cliOutput.js");
const MAX_STDOUT_CHARS = 16 * 1024 * 1024;
const writeMessage = (message) => {
process.stdout.write(`${JSON.stringify(message)}\n`);
};
// One CLI instance serves every command. A fresh instance per command retains
// its command registrations after the call returns; production exits instead.
const outputParts = [];
let outputCharacters = 0;
const destination = new Writable({
write(chunk, _encoding, callback) {
const text = Buffer.isBuffer(chunk)
? chunk.toString("utf8")
: String(chunk);
outputCharacters += text.length;
if (outputCharacters > MAX_STDOUT_CHARS) {
callback(new Error("Compiled CLI stdout exceeded 16 MiB"));
return;
}
outputParts.push(text);
callback();
},
});
Object.defineProperty(destination, "isTTY", { value: false });
const resultOutput = createCliResultOutput(
parseCliOutputArguments(["--json"]),
destination,
);
const cli = createCli(process.env, resultOutput);
const resetOutput = () => {
outputParts.length = 0;
outputCharacters = 0;
};
const runCommand = async (args) => {
const previousExitCode = process.exitCode;
process.exitCode = undefined;
resetOutput();
try {
const cliArgs = normalizeCliArguments(args);
const outputOptions = parseCliOutputArguments(cliArgs);
const outputArguments = outputOptions.validation;
if (!outputArguments.ok)
return {
exitCode: 1,
stdout: renderCliOutputArgumentError(outputArguments),
};
const configurationError = cliConfigurationError(process.env);
if (configurationError !== undefined)
return { exitCode: 1, stdout: "", error: configurationError };
let wroteOutput = false;
let explicitExit;
resultOutput?.begin(outputOptions);
try {
await cli.serve(cliArgs, {
stdout: (output) => {
if (resultOutput?.handled) return;
if (output.length === 0) return;
wroteOutput = true;
if (resultOutput?.captureSdkOutput(output)) return;
destination.write(output);
},
exit: (code) => {
explicitExit = code;
},
});
} catch (cause) {
return {
exitCode: 1,
stdout: outputParts.join(""),
error:
cause instanceof Error
? (cause.stack ?? cause.message)
: String(cause),
};
}
if (!wroteOutput && !resultOutput?.handled) {
const empty = renderEmptyFilteredCliOutput(outputOptions);
if (empty !== undefined) destination.write(empty);
}
await resultOutput?.finishSdkOutput(
explicitExit ??
(typeof process.exitCode === "number" ? process.exitCode : 0),
);
// logCliCommand reports some failures only through process.exitCode.
return {
exitCode:
explicitExit ??
(typeof process.exitCode === "number" ? process.exitCode : 0),
stdout: outputParts.join(""),
};
} finally {
process.exitCode = previousExitCode;
}
};
const acceptRequest = async (line) => {
let request;
try {
request = JSON.parse(line);
} catch (cause) {
writeMessage({
id: -1,
exitCode: 1,
stdout: "",
error: cause instanceof Error ? cause.message : String(cause),
});
return;
}
const args = request?.args;
if (
typeof request?.id !== "number" ||
!Array.isArray(args) ||
args.some((argument) => typeof argument !== "string")
) {
writeMessage({
id: typeof request?.id === "number" ? request.id : -1,
exitCode: 1,
stdout: "",
error: "Compiled CLI session expected an id and string arguments",
});
return;
}
writeMessage({ id: request.id, ...(await runCommand(args)) });
};
const lines = createInterface({ input: process.stdin, crlfDelay: Infinity });
writeMessage({ ready: true });
for await (const line of lines) {
if (line.trim().length === 0) continue;
await acceptRequest(line);
}
await closeCliJavaScriptResources();