Files
rea/src/config/parseConfig.ts
T
7582f0dff2 fix(hopper): share launcher discovery across setup and runtime
Centralize canonical system/user and deterministic renamed-bundle path facts. Reuse the installer destination and honor the same local candidates in doctor and runtime; scan renamed bundles only after canonical candidates fail. Preserve explicit path spelling and exclusive selection. Homebrew command probing remains doctor-only with explicit runtime configuration guidance.

Validation: owner, config, setup and doctor tests cover canonical priority and renamed paths; public CLI verifies explicit path preservation without launching Hopper. No real Hopper analysis claimed.

Fixes #3054
Related: #3085

Co-authored-by: Vinícius DS Andrade <95101505+viniciusdsandrade@users.noreply.github.com>
Co-authored-by: Kaoru0822 <208281322+kaoru0822-kitauji@users.noreply.github.com>
2026-10-14 17:09:17 +08:00

99 lines
3.4 KiB
TypeScript

import { accessSync, constants } 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 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 true;
} 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
);
};
/** Parse provider configuration and runtime prerequisites at the composition root. */
export const parseConfig = (
environment: Readonly<Record<string, string | undefined>>,
): Result<AppConfig, ConfigurationError> => {
const parsed = parseEnvironment(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 }),
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,
},
});
};