fix(cli): report an invalid REA_LOG_LEVEL as configuration (#2140)
createCli parsed REA_LOG_LEVEL with a throwing schema, so an invalid or uppercase value crashed every command, including doctor and --help, with a ZodError stack trace. Parse it into a ConfigurationError result and have the executable print the same configuration message as the MCP adapter and exit 1 before building the CLI. Fixes #2135 Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
4f46eac453
commit
3159732597
|
|
@ -58,7 +58,8 @@ if (!(await compiledRuntimeExists(runtimeFiles))) {
|
|||
process.stdout.write(result.output);
|
||||
process.exitCode = result.exitCode;
|
||||
} else {
|
||||
const { createCli } = await import("../dist/cli-runtime/cli.js");
|
||||
const { cliConfigurationError, createCli } =
|
||||
await import("../dist/cli-runtime/cli.js");
|
||||
const { createStreamedCliJsonOutput } =
|
||||
await import("../dist/cli-runtime/streamedJsonOutput.js");
|
||||
const {
|
||||
|
|
@ -67,7 +68,11 @@ if (!(await compiledRuntimeExists(runtimeFiles))) {
|
|||
validateCliOutputArguments,
|
||||
} = await import("../dist/cli-runtime/cliOutput.js");
|
||||
const outputArguments = validateCliOutputArguments(args);
|
||||
if (!outputArguments.ok) {
|
||||
const configurationError = cliConfigurationError(process.env);
|
||||
if (configurationError !== undefined) {
|
||||
process.stderr.write(`${configurationError}\n`);
|
||||
process.exitCode = 1;
|
||||
} else if (!outputArguments.ok) {
|
||||
process.stdout.write(renderCliOutputArgumentError(outputArguments));
|
||||
process.exitCode = 1;
|
||||
} else {
|
||||
|
|
|
|||
20
src/cli.ts
20
src/cli.ts
|
|
@ -8,6 +8,7 @@ import { registerBinaryDiagnosticsCommands } from "./cli/binaryDiagnosticsComman
|
|||
import { Cli } from "incur";
|
||||
|
||||
import { createLogger, parseLogLevel } from "./logger.js";
|
||||
import { projectAnalysisError } from "./domain/analysisErrorProjection.js";
|
||||
import { PRODUCT_IDENTITY } from "./identity.js";
|
||||
import { npxRegistrationCommand } from "./application/ClientRegistrationIdentity.js";
|
||||
import { registerSetupCommands } from "./cli/setupCommands.js";
|
||||
|
|
@ -41,6 +42,16 @@ import {
|
|||
type CliResultOutput,
|
||||
} from "./cli/streamedJsonOutput.js";
|
||||
|
||||
/** Describe CLI settings that are invalid before any command runs, as MCP does. */
|
||||
export const cliConfigurationError = (
|
||||
selectedEnvironment: Readonly<Record<string, string | undefined>>,
|
||||
): string | undefined => {
|
||||
const value = selectedEnvironment.REA_LOG_LEVEL;
|
||||
if (value === undefined) return undefined;
|
||||
const level = parseLogLevel(value);
|
||||
return level.ok ? undefined : projectAnalysisError(level.error).message;
|
||||
};
|
||||
|
||||
/**
|
||||
* Build the one-shot Incur CLI without starting Hopper at import time.
|
||||
* Analysis commands acquire and close their own sessions; bare `mcp` and
|
||||
|
|
@ -54,11 +65,14 @@ export const createCli = (
|
|||
): CliInstance => {
|
||||
const environment = snapshotEnvironment(selectedEnvironment);
|
||||
const analysis = createDirectAnalysis(environment);
|
||||
// The executable reports an invalid level through cliConfigurationError.
|
||||
const level =
|
||||
environment.REA_LOG_LEVEL === undefined
|
||||
? undefined
|
||||
: parseLogLevel(environment.REA_LOG_LEVEL);
|
||||
const logger = createLogger(
|
||||
"cli",
|
||||
environment.REA_LOG_LEVEL === undefined
|
||||
? "silent"
|
||||
: parseLogLevel(environment.REA_LOG_LEVEL),
|
||||
level?.ok === true ? level.value : "silent",
|
||||
);
|
||||
const cli = Cli.create(PRODUCT_IDENTITY.cliBinary, {
|
||||
version: PRODUCT_IDENTITY.packageVersion,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
import pino, { type Logger } from "pino";
|
||||
import { z } from "zod";
|
||||
|
||||
import { ConfigurationError } from "./domain/configurationErrors.js";
|
||||
import { err, ok, type Result } from "./domain/result.js";
|
||||
|
||||
/** Supported structured log levels at the process boundary. */
|
||||
export type LogLevel = z.infer<typeof logLevelSchema>;
|
||||
|
||||
|
|
@ -9,8 +12,21 @@ const logLevelSchema = z
|
|||
.default("info");
|
||||
|
||||
/** Parse the optional REA_LOG_LEVEL environment variable. */
|
||||
export const parseLogLevel = (value: unknown): LogLevel =>
|
||||
logLevelSchema.parse(value);
|
||||
export const parseLogLevel = (
|
||||
value: unknown,
|
||||
): Result<LogLevel, ConfigurationError> => {
|
||||
const parsed = logLevelSchema.safeParse(value);
|
||||
if (parsed.success) return ok(parsed.data);
|
||||
return err(
|
||||
new ConfigurationError("Invalid REA environment configuration", {
|
||||
cause: parsed.error,
|
||||
settings: parsed.error.issues.map(({ message }) => ({
|
||||
setting: "REA_LOG_LEVEL",
|
||||
constraint: message,
|
||||
})),
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
/** Create a JSON logger on stderr so protocol and CLI result stdout stay valid. */
|
||||
export const createLogger = (mode: "mcp" | "cli", level: LogLevel): Logger =>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,21 @@
|
|||
import { expect } from "vitest";
|
||||
|
||||
import { cliTest } from "../../support/cli/cliFixture.js";
|
||||
|
||||
for (const level of ["loud", "INFO"])
|
||||
cliTest(
|
||||
`reports REA_LOG_LEVEL=${level} as invalid configuration`,
|
||||
async ({ cli }) => {
|
||||
for (const arguments_ of [["doctor", "--json"], ["--help"]]) {
|
||||
const result = await cli.run({
|
||||
arguments: arguments_,
|
||||
environment: { REA_LOG_LEVEL: level },
|
||||
});
|
||||
expect(result.exitCode).toBe(1);
|
||||
expect(result.stdout).toBe("");
|
||||
expect(result.stderr).toBe(
|
||||
'REA configuration is invalid: REA_LOG_LEVEL: Invalid option: expected one of "trace"|"debug"|"info"|"warn"|"error"|"fatal"|"silent".\n',
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
|
@ -25,7 +25,7 @@ describe("executable dispatcher", () => {
|
|||
),
|
||||
workspace.write(
|
||||
"dist/cli-runtime/cli.js",
|
||||
'export const createCli = () => ({ serve: (args) => process.stdout.write(JSON.stringify({ adapter: "cli", args })) });\n',
|
||||
'export const cliConfigurationError = () => undefined; export const createCli = () => ({ serve: (args) => process.stdout.write(JSON.stringify({ adapter: "cli", args })) });\n',
|
||||
),
|
||||
workspace.write(
|
||||
"dist/cli-runtime/cliOutput.js",
|
||||
|
|
|
|||
Loading…
Reference in New Issue