fix(setup): preserve selected Hopper loader arguments

This commit is contained in:
morluto committed 2026-10-27 07:03:11 +08:00
1 parent 2dd2ffcd45
commit 077de92594
3 files changed
+78

No files matched your search

@@ -7,6 +7,7 @@ const SELECTED_REGISTRATION_SETTINGS = [
"GHIDRA_MAXMEM",
"JAVA_HOME",
"HOPPER_LAUNCHER_PATH",
"HOPPER_LOADER_ARGS_JSON",
"REA_ILSPY_CMD_PATH",
"REA_ADB_PATH",
"REA_APKTOOL_COMMAND",
@@ -26,6 +26,7 @@ cliTest(
const config = join(home, ".qwen/settings.json");
const selected = {
HOPPER_LAUNCHER_PATH: join(home, "missing-hopper"),
HOPPER_LOADER_ARGS_JSON: '["", "--loader", "Mach-O"]',
GHIDRA_INSTALL_DIR: join(home, "offline-ghidra"),
GHIDRA_HEADLESS_MAXMEM: "512M",
GHIDRA_MAXMEM: "3G",
@@ -111,6 +112,8 @@ cliTest(
environment.REA_MCP_TOOL_FAMILIES = selected.REA_MCP_TOOL_FAMILIES;
selected.REA_OBJDUMP_COMMAND = join(home, "replacement-objdump");
environment.REA_OBJDUMP_COMMAND = selected.REA_OBJDUMP_COMMAND;
selected.HOPPER_LOADER_ARGS_JSON = "[]";
environment.HOPPER_LOADER_ARGS_JSON = selected.HOPPER_LOADER_ARGS_JSON;
// Empty headless heap explicitly selects the lower-priority caller value.
selected.GHIDRA_HEADLESS_MAXMEM = "";
selected.GHIDRA_MAXMEM = "768M";
@@ -0,0 +1,74 @@
import { mkdir, readFile, writeFile } from "node:fs/promises";
import { join } from "node:path";
import { expect, it } from "vitest";
import { createCli } from "../../../src/cli.js";
import { parseConfig } from "../../../src/config/parseConfig.js";
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
it("setup carries explicit Hopper loader selections into Cursor without persisting target or unrelated settings", async () => {
const home = await createTestTempDirectory("rea-setup-loader-environment-");
const path = join(home, ".cursor/mcp.json");
await mkdir(join(home, ".cursor"));
await writeFile(path, '{"caller":"retain"}\n');
const environment = {
HOME: home,
USERPROFILE: home,
HOPPER_LAUNCHER_PATH: join(home, "missing-hopper"),
GHIDRA_INSTALL_DIR: join(home, "missing-ghidra"),
JAVA_HOME: join(home, "missing-java"),
HOPPER_LOADER_ARGS_JSON: '["", "--loader", "Mach-O"]',
HOPPER_TARGET_PATH: join(home, "per-target"),
HOPPER_TARGET_KIND: "executable",
CALLER_UNRELATED_SETTING: "do not persist",
};
const setup = async () => {
let stdout = "";
await createCli(environment).serve(
["setup", "--client", "cursor", "--skill=false", "--yes", "--json"],
{
env: environment,
stdout: (chunk) => {
stdout += chunk;
},
exit: () => undefined,
},
);
return JSON.parse(stdout) as unknown;
};
const loaderFromSavedRegistration = async () => {
const document = JSON.parse(await readFile(path, "utf8"));
expect(document.caller).toBe("retain");
const saved = document.mcpServers.rea.env;
expect(saved).not.toHaveProperty("CALLER_UNRELATED_SETTING");
expect(saved).not.toHaveProperty("HOPPER_TARGET_PATH");
expect(saved).not.toHaveProperty("HOPPER_TARGET_KIND");
const config = parseConfig(saved);
if (!config.ok) throw config.error;
return config.value.hopperLoaderArgs;
};
expect(await setup()).toMatchObject({ status: "ready" });
expect(await loaderFromSavedRegistration()).toEqual([
"",
"--loader",
"Mach-O",
]);
const created = await readFile(path, "utf8");
expect(JSON.parse(created).mcpServers.rea.env.HOPPER_LOADER_ARGS_JSON).toBe(
environment.HOPPER_LOADER_ARGS_JSON,
);
environment.HOPPER_LOADER_ARGS_JSON = "[]";
expect(await setup()).toMatchObject({ status: "ready" });
expect(await loaderFromSavedRegistration()).toEqual([]);
const updated = await readFile(path, "utf8");
expect(JSON.parse(updated).mcpServers.rea.env.HOPPER_LOADER_ARGS_JSON).toBe(
"[]",
);
environment.HOPPER_LOADER_ARGS_JSON = "{";
expect(await setup()).toMatchObject({
status: "needs_human",
plannedActions: [],
appliedActions: [],
});
expect(await readFile(path, "utf8")).toBe(updated);
});