import { accessSync, constants, statSync } from "node:fs"; import type { ConfigurationError } from "../domain/configurationErrors.js"; import { ok, type Result } from "../domain/result.js"; import { parseEnvironment } from "./environment.js"; import { homeDirectoryFromEnvironment } from "./homeDirectory.js"; import { DEFAULT_BRIDGE_MAX_REQUEST } from "./bridgeMaxRequestBytes.js"; import { DEFAULT_REGEX_MATCHING_DEADLINE } from "./regexMatchingDeadline.js"; import { DEFAULT_REGEX_WORKER_HEAP_REQUEST } from "./regexWorkerHeapRequest.js"; import type { AppConfig } from "./types.js"; import { linuxHopperLauncherCandidates, REA_MANAGED_LINUX_HOPPER_LAUNCHER, } from "../application/LinuxHopper.js"; import { MAC_HOPPER_SYSTEM_LAUNCHER, macHopperLauncherCandidates, } from "../application/MacHopperPaths.js"; const executableAvailable = (path: string): boolean => { try { accessSync(path, constants.X_OK); return statSync(path).isFile(); } catch { return false; } }; export const defaultHopperLauncherPath = ( platform: NodeJS.Platform, homeDirectory: string, executable: (path: string) => boolean = executableAvailable, ): string => { if (platform === "darwin") { for (const candidate of macHopperLauncherCandidates(homeDirectory)) if (executable(candidate)) return candidate; return MAC_HOPPER_SYSTEM_LAUNCHER; } if (platform !== "linux") return MAC_HOPPER_SYSTEM_LAUNCHER; return ( linuxHopperLauncherCandidates(homeDirectory).find(executable) ?? REA_MANAGED_LINUX_HOPPER_LAUNCHER ); }; /** * Environment native analysis admits. JEB validates its own timeout during * optional provider composition; that setting must not block unrelated * CLI/MCP startup. Registration validation still checks it directly. */ const nativeAnalysisEnvironment = ( environment: Readonly>, ): Readonly> => { const { REA_JEB_MCP_TIMEOUT_MS: _jebTimeout, ...appEnvironment } = environment; return appEnvironment; }; /** * The configuration error native commands return, without probing launchers. * Doctor uses this so readiness and analysis admit the same settings. */ export const nativeAnalysisConfigurationError = ( environment: Readonly>, ): ConfigurationError | undefined => { const parsed = parseEnvironment(nativeAnalysisEnvironment(environment)); return parsed.ok ? undefined : parsed.error; }; /** Parse provider configuration and runtime prerequisites at the composition root. */ export const parseConfig = ( environment: Readonly>, ): Result => { const parsed = parseEnvironment(nativeAnalysisEnvironment(environment)); if (!parsed.ok) return parsed; const env = parsed.value; return ok({ ...(env.REA_MCP_MAX_RESPONSE_BYTES === undefined ? {} : { mcpMaxResponseBytes: env.REA_MCP_MAX_RESPONSE_BYTES }), ...(env.REA_MCP_INPUT_SCHEMA_PROFILE === undefined ? {} : { mcpInputSchemaProfile: env.REA_MCP_INPUT_SCHEMA_PROFILE }), ...(env.REA_MCP_OUTPUT_SCHEMA_PROFILE === undefined ? {} : { mcpOutputSchemaProfile: env.REA_MCP_OUTPUT_SCHEMA_PROFILE }), analysisProvider: env.REA_ANALYSIS_PROVIDER, ...(env.REA_IDA_MCP_CONFIG === undefined ? {} : { idaMcpConfigPath: env.REA_IDA_MCP_CONFIG }), ghidraInstallDir: env.GHIDRA_INSTALL_DIR, ghidraJavaHome: env.JAVA_HOME, ghidraStartupTimeoutMs: env.REA_GHIDRA_STARTUP_TIMEOUT_MS, ...(env.REA_GHIDRA_LANGUAGE_ID === undefined ? {} : { ghidraLanguageOverride: { languageId: env.REA_GHIDRA_LANGUAGE_ID, ...(env.REA_GHIDRA_COMPILER_SPEC_ID === undefined ? {} : { compilerSpecId: env.REA_GHIDRA_COMPILER_SPEC_ID }), }, }), ...(env.REA_GHIDRA_SEED_FILE === undefined ? {} : { ghidraSeedFile: env.REA_GHIDRA_SEED_FILE }), ...(env.REA_GHIDRA_DISABLED_ANALYZERS === undefined ? {} : { ghidraDisabledAnalyzers: env.REA_GHIDRA_DISABLED_ANALYZERS }), ...(env.REA_GHIDRA_NATIVEAOT_JAR === undefined ? {} : { ghidraNativeAotJar: env.REA_GHIDRA_NATIVEAOT_JAR }), ilspyCmdPath: env.REA_ILSPY_CMD_PATH, hopperLauncherPath: env.HOPPER_LAUNCHER_PATH ?? defaultHopperLauncherPath( process.platform, homeDirectoryFromEnvironment(environment, process.platform), ), hopperStartupTimeoutMs: env.REA_HOPPER_STARTUP_TIMEOUT_MS, hopperTargetPath: env.HOPPER_TARGET_PATH, hopperTargetKind: env.HOPPER_TARGET_KIND, hopperLoaderArgs: env.loaderArgs, logLevel: env.REA_LOG_LEVEL, referenceSourcePolicy: { secretPatterns: env.referenceSecretPatterns, }, bridgeMaxRequestBytes: env.REA_BRIDGE_MAX_REQUEST_BYTES === undefined ? DEFAULT_BRIDGE_MAX_REQUEST : { maximumBytes: env.REA_BRIDGE_MAX_REQUEST_BYTES, source: "setting", }, regexMatchingDeadline: env.REA_REGEX_MATCHING_DEADLINE_MS === undefined ? DEFAULT_REGEX_MATCHING_DEADLINE : { deadlineMs: env.REA_REGEX_MATCHING_DEADLINE_MS, source: "setting", }, regexWorkerHeapRequest: env.REA_REGEX_WORKER_HEAP_REQUEST_MIB === undefined ? DEFAULT_REGEX_WORKER_HEAP_REQUEST : { heapMiB: env.REA_REGEX_WORKER_HEAP_REQUEST_MIB, source: "setting", }, }); };