mirror of
https://github.com/morluto/rea.git
synced 2026-10-19 04:19:54 -05:00
refactor(setup): distinguish provider configuration from registration settings
This commit is contained in:
1 parent
333e0ab0df
commit
4946688c33
8 files changed
+94
-16
No files matched your search
@@ -206,9 +206,10 @@ describe("configured Linux Hopper selection", () => {
|
||||
host.hopperLauncherConfigured = true;
|
||||
host.linuxDemoRuntimeMissing = true;
|
||||
const doctor = await host.doctor();
|
||||
const initial = await host.initialSetupState();
|
||||
const discovery = await discoverSetupState({
|
||||
host,
|
||||
providerEnvironment: { HOPPER_LAUNCHER_PATH: host.hopper },
|
||||
providerConfigured: initial.providerConfigured,
|
||||
forceHopperInstall: false,
|
||||
proposeHopper: true,
|
||||
hopperLauncherConfigured: true,
|
||||
@@ -227,9 +228,10 @@ describe("configured Linux Hopper selection", () => {
|
||||
};
|
||||
host.hopper = "/opt/hopper/bin/Hopper";
|
||||
host.unsupportedHopperVersion = true;
|
||||
const initial = await host.initialSetupState();
|
||||
const discovery = await discoverSetupState({
|
||||
host,
|
||||
providerEnvironment: { HOPPER_LAUNCHER_PATH: host.hopper },
|
||||
providerConfigured: initial.providerConfigured,
|
||||
forceHopperInstall: false,
|
||||
proposeHopper: true,
|
||||
initialDoctor: await host.doctor(),
|
||||
|
||||
@@ -74,6 +74,7 @@ export class FakeSetupHost implements SetupHost {
|
||||
...(this.hopperLauncherConfigured
|
||||
? { hopperLauncherConfigured: true }
|
||||
: {}),
|
||||
providerConfigured: this.hopper !== undefined || this.ghidra !== undefined,
|
||||
providerEnvironment: {
|
||||
...(this.hopper === undefined
|
||||
? {}
|
||||
|
||||
@@ -111,7 +111,7 @@ const runSetupWorkflow = async (
|
||||
let { hopperPath, providerEnvironment } = initialState;
|
||||
const discovery = await discoverSetupState({
|
||||
host,
|
||||
providerEnvironment,
|
||||
providerConfigured: initialState.providerConfigured,
|
||||
forceHopperInstall: options.installHopper,
|
||||
hopperLauncherConfigured: initialState.hopperLauncherConfigured === true,
|
||||
proposeHopper:
|
||||
|
||||
@@ -60,6 +60,7 @@ import type {
|
||||
} from "./SetupTypes.js";
|
||||
import type { DoctorScope } from "./Doctor.js";
|
||||
import { CURRENT_ENTRYPOINT_PATH } from "../process/CurrentEntrypointPath.js";
|
||||
import { analysisProviderSelectorSchema } from "../contracts/providerSelection.js";
|
||||
|
||||
/** Resolve the executable and arguments used in a managed MCP registration. */
|
||||
export const setupRegistrationCommand = (
|
||||
@@ -169,6 +170,17 @@ export const systemSetupHost = (
|
||||
scope?: DoctorScope,
|
||||
): Promise<SetupInitialState> => {
|
||||
const diagnosis = await runDoctor(undefined, doctorHost, scope);
|
||||
const providerEnvironment: SetupProviderEnvironment = {
|
||||
...providerRegistrationEnvironment(diagnosis.providerInspections ?? []),
|
||||
...doctorHost.providerRegistrationEnvironment?.(),
|
||||
...explicitSetupRegistrationEnvironment(environment),
|
||||
...(diagnosis.hopperPath === undefined
|
||||
? {}
|
||||
: { HOPPER_LAUNCHER_PATH: diagnosis.hopperPath }),
|
||||
};
|
||||
const requestedProvider = analysisProviderSelectorSchema.safeParse(
|
||||
environment.REA_ANALYSIS_PROVIDER,
|
||||
);
|
||||
return {
|
||||
...(diagnosis.hopperPath === undefined
|
||||
? {}
|
||||
@@ -178,16 +190,14 @@ export const systemSetupHost = (
|
||||
diagnosis.hopperPath === environment.HOPPER_LAUNCHER_PATH
|
||||
? { hopperLauncherConfigured: true }
|
||||
: {}),
|
||||
providerEnvironment: {
|
||||
...providerRegistrationEnvironment(
|
||||
diagnosis.providerInspections ?? [],
|
||||
),
|
||||
...doctorHost.providerRegistrationEnvironment?.(),
|
||||
...explicitSetupRegistrationEnvironment(environment),
|
||||
...(diagnosis.hopperPath === undefined
|
||||
? {}
|
||||
: { HOPPER_LAUNCHER_PATH: diagnosis.hopperPath }),
|
||||
},
|
||||
providerConfigured:
|
||||
diagnosis.hopperPath !== undefined ||
|
||||
diagnosis.providerInspections?.some(
|
||||
({ configured }) => configured,
|
||||
) === true ||
|
||||
providerEnvironment.REA_JEB_MCP_URL !== undefined ||
|
||||
(requestedProvider.success && requestedProvider.data !== "auto"),
|
||||
providerEnvironment,
|
||||
doctor: diagnosis,
|
||||
};
|
||||
},
|
||||
|
||||
@@ -37,7 +37,7 @@ export interface SetupDiscovery {
|
||||
/** Discover supported clients and current registrations without mutation. */
|
||||
export const discoverSetupState = async (input: {
|
||||
readonly host: SetupHost;
|
||||
readonly providerEnvironment: SetupProviderEnvironment;
|
||||
readonly providerConfigured: boolean;
|
||||
readonly forceHopperInstall: boolean;
|
||||
readonly hopperLauncherConfigured?: boolean;
|
||||
readonly proposeHopper: boolean;
|
||||
@@ -65,8 +65,7 @@ export const discoverSetupState = async (input: {
|
||||
input.forceHopperInstall ||
|
||||
(hopperInstallSupported &&
|
||||
input.proposeHopper &&
|
||||
(Object.keys(input.providerEnvironment).length === 0 ||
|
||||
linuxHopperRepairNeeded));
|
||||
(!input.providerConfigured || linuxHopperRepairNeeded));
|
||||
const [clients, detectedClients] = await Promise.all([
|
||||
input.host.supportedClients(),
|
||||
input.host.detectedClients(),
|
||||
|
||||
@@ -20,6 +20,8 @@ export interface SetupInitialState {
|
||||
readonly hopperPath?: string;
|
||||
/** Whether the caller explicitly selected a Hopper launcher in its environment. */
|
||||
readonly hopperLauncherConfigured?: boolean;
|
||||
/** Whether the caller or host has an analysis provider configuration. */
|
||||
readonly providerConfigured: boolean;
|
||||
readonly providerEnvironment: SetupProviderEnvironment;
|
||||
readonly doctor: DoctorReport;
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import { afterEach, expect, it, vi } from "vitest";
|
||||
import { createDoctorHostFixture } from "./application/Doctor.fixture.js";
|
||||
import * as macHopperInstaller from "./application/MacHopper.js";
|
||||
import { runSetup } from "./application/Setup.js";
|
||||
import { discoverSetupState } from "./application/SetupPlan.js";
|
||||
import { options } from "./application/Setup.fixture.js";
|
||||
import { systemSetupHost } from "./application/SetupHost.js";
|
||||
import { systemUninstallHost } from "./application/Uninstall.js";
|
||||
@@ -176,6 +177,60 @@ it("preserves a validated selected JEB endpoint in setup registrations without c
|
||||
}
|
||||
});
|
||||
|
||||
it.each([
|
||||
{
|
||||
name: "registration-only settings",
|
||||
environment: {
|
||||
REA_ANALYSIS_PROVIDER: "auto",
|
||||
REA_GHIDRA_STARTUP_TIMEOUT_MS: "2700000",
|
||||
REA_MCP_INPUT_SCHEMA_PROFILE: "full",
|
||||
},
|
||||
providerConfigured: false,
|
||||
installHopper: true,
|
||||
},
|
||||
{
|
||||
name: "an explicit unready provider selection",
|
||||
environment: { REA_ANALYSIS_PROVIDER: "ghidra" },
|
||||
providerConfigured: true,
|
||||
installHopper: false,
|
||||
},
|
||||
])(
|
||||
"derives Hopper proposal behavior from setup-host facts for $name",
|
||||
async ({ environment, providerConfigured, installHopper }) => {
|
||||
const doctorHost = createDoctorHostFixture({
|
||||
platform: "darwin",
|
||||
validTarget: () => Promise.resolve(false),
|
||||
executable: () => Promise.resolve(false),
|
||||
});
|
||||
const setupHost = systemSetupHost(doctorHost, {
|
||||
HOME: "/fixture/home",
|
||||
USERPROFILE: "/fixture/home",
|
||||
...environment,
|
||||
});
|
||||
try {
|
||||
const initial = await setupHost.initialSetupState();
|
||||
expect(initial.providerConfigured).toBe(providerConfigured);
|
||||
if ("REA_MCP_INPUT_SCHEMA_PROFILE" in environment) {
|
||||
expect(initial.providerEnvironment).toMatchObject({
|
||||
REA_ANALYSIS_PROVIDER: "auto",
|
||||
REA_GHIDRA_STARTUP_TIMEOUT_MS: "2700000",
|
||||
REA_MCP_INPUT_SCHEMA_PROFILE: "full",
|
||||
});
|
||||
}
|
||||
const discovery = await discoverSetupState({
|
||||
host: setupHost,
|
||||
providerConfigured: initial.providerConfigured,
|
||||
forceHopperInstall: false,
|
||||
proposeHopper: true,
|
||||
initialDoctor: initial.doctor,
|
||||
});
|
||||
expect(discovery.installHopper).toBe(installHopper);
|
||||
} finally {
|
||||
await setupHost.close?.();
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
it("passes only selected environment to updater subprocesses and drops npm invocation identity", async () => {
|
||||
vi.stubEnv("REA_UPDATE_MARKER", "ambient");
|
||||
const result = await runUpdateCommand(
|
||||
|
||||
@@ -357,6 +357,15 @@ for (const invalidSetting of [
|
||||
environment,
|
||||
});
|
||||
expect(result.exitCode).toBe(1);
|
||||
expect(result.json).toMatchObject({
|
||||
code: "configuration_invalid",
|
||||
category: "invalid_input",
|
||||
details: {
|
||||
settings: [
|
||||
expect.objectContaining({ setting: invalidSetting.setting }),
|
||||
],
|
||||
},
|
||||
});
|
||||
expect(`${result.stdout}\n${result.stderr}`).toContain(
|
||||
invalidSetting.setting,
|
||||
);
|
||||
|
||||
Reference in new issue
Block a user