diff --git a/src/cli/electronCommands.ts b/src/cli/electronCommands.ts index 12ef5591e1..d9d1e4d11e 100644 --- a/src/cli/electronCommands.ts +++ b/src/cli/electronCommands.ts @@ -217,10 +217,16 @@ const registerElectronTargetList = ( withCommandCancellation((signal) => logCliCommand(logger, "list-electron-targets", async () => { const context = await electronObservationContext(); - const parsed = listElectronTargetsInputSchema.safeParse({ + const input = { cdp_endpoint: args.endpoint, - }); - if (!parsed.success) return inputError("list_electron_targets"); + }; + const parsed = listElectronTargetsInputSchema.safeParse(input); + if (!parsed.success) + return inputError( + "list_electron_targets", + parsed.error.issues, + input, + ); const { listElectronTargets } = await import("../application/javascript/ElectronObservationService.js"); return await finishAnalysisCommand( @@ -255,13 +261,19 @@ const registerElectronPageInspection = ( withCommandCancellation((signal) => logCliCommand(logger, "inspect-electron-page", async () => { const context = await electronObservationContext(); - const parsed = inspectElectronPageInputSchema.safeParse({ + const input = { cdp_endpoint: args.endpoint, target_id: args.targetId, observation_ms: options.observationMs, include_script_sources: options.includeScriptSources, - }); - if (!parsed.success) return inputError("inspect_electron_page"); + }; + const parsed = inspectElectronPageInputSchema.safeParse(input); + if (!parsed.success) + return inputError( + "inspect_electron_page", + parsed.error.issues, + input, + ); const { inspectElectronPage } = await import("../application/javascript/ElectronObservationService.js"); return await finishAnalysisCommand( diff --git a/tests/boundary/cli/electronCli.test.ts b/tests/boundary/cli/electronCli.test.ts index 04cba6f66e..2733ee6a06 100644 --- a/tests/boundary/cli/electronCli.test.ts +++ b/tests/boundary/cli/electronCli.test.ts @@ -96,6 +96,53 @@ it( INTEGRATION_TEST_TIMEOUT_MS, ); +it( + "reports rejected Electron CLI arguments with structured issue details", + async () => { + const invalidTargetList = await runCliResult( + ["list-electron-targets", "not a url", "--json"], + process.env, + ); + expect(invalidTargetList.exitCode).toBe(1); + expect(invalidTargetList.result).toMatchObject({ + error: "Electron analysis failed", + code: "invalid_request", + category: "invalid_input", + details: { + operation: "list_electron_targets", + issues: [ + { + path: ["cdp_endpoint"], + reason: "invalid_value", + }, + ], + }, + }); + + const invalidPageInspect = await runCliResult( + ["inspect-electron-page", "http://127.0.0.1:9222", "", "--json"], + process.env, + ); + expect(invalidPageInspect.exitCode).toBe(1); + expect(invalidPageInspect.result).toMatchObject({ + error: "Electron analysis failed", + code: "invalid_request", + category: "invalid_input", + details: { + operation: "inspect_electron_page", + issues: [ + { + path: ["target_id"], + reason: "out_of_range", + minimum: 1, + }, + ], + }, + }); + }, + INTEGRATION_TEST_TIMEOUT_MS, +); + // On Windows child.kill("SIGINT") terminates the process without a console signal. it.skipIf(process.platform === "win32")( "forwards SIGINT through Electron page inspection and completes target cleanup", @@ -436,25 +483,32 @@ describe("Electron CLI runtime reconciliation", () => { const isRecord = (value: unknown): value is Record => typeof value === "object" && value !== null && !Array.isArray(value); -const runCli = async ( +const runCliResult = async ( arguments_: readonly string[], environment: NodeJS.ProcessEnv, -): Promise => { +): Promise<{ readonly exitCode: number; readonly result: unknown }> => { try { const { stdout } = await execute( process.execPath, ["scripts/rea.mjs", ...arguments_], { cwd: process.cwd(), env: environment, maxBuffer: 16 * 1_024 * 1_024 }, ); - return JSON.parse(stdout); + return { exitCode: 0, result: JSON.parse(stdout) }; } catch (cause: unknown) { if ( typeof cause === "object" && cause !== null && "stdout" in cause && - typeof cause.stdout === "string" + typeof cause.stdout === "string" && + "code" in cause && + typeof cause.code === "number" ) - return JSON.parse(cause.stdout); + return { exitCode: cause.code, result: JSON.parse(cause.stdout) }; throw cause; } }; + +const runCli = async ( + arguments_: readonly string[], + environment: NodeJS.ProcessEnv, +): Promise => (await runCliResult(arguments_, environment)).result;