mirror of
https://github.com/morluto/rea.git
synced 2026-10-26 17:35:11 +00:00
261 lines
9.2 KiB
TypeScript
261 lines
9.2 KiB
TypeScript
import {
|
|
access,
|
|
mkdir,
|
|
readFile,
|
|
realpath,
|
|
rm,
|
|
symlink,
|
|
writeFile,
|
|
} from "node:fs/promises";
|
|
import { dirname, join } from "node:path";
|
|
import { expect, it } from "vitest";
|
|
|
|
import { setupRegistrationCommand } from "../../../src/application/SetupHost.js";
|
|
import {
|
|
classifyClientRegistrationCommand,
|
|
registrationRuntimePath,
|
|
} from "../../../src/application/ClientRegistrationIdentity.js";
|
|
import { readClientRegistrationInspections } from "../../../src/application/ClientRegistrationStatus.js";
|
|
import { javaScriptRuntime } from "../../../src/domain/runtimeVersion.js";
|
|
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
|
|
|
|
it.skipIf(process.platform === "win32")(
|
|
"registers a same-file PATH launcher whose manager-owned alias survives a version switch",
|
|
async () => {
|
|
const root = await createTestTempDirectory("rea-stable-launcher-alias-");
|
|
const installs = join(root, "installs");
|
|
const old = join(installs, "old");
|
|
const next = join(installs, "next");
|
|
const scriptWithin = (tag: string) =>
|
|
join("node_modules", ".mise", `rea-agents@${tag}`, "scripts", "rea.mjs");
|
|
for (const [version, tag] of [
|
|
[old, "old"],
|
|
[next, "next"],
|
|
] as const) {
|
|
await mkdir(join(version, "bin"), { recursive: true });
|
|
const script = join(version, scriptWithin(tag));
|
|
await mkdir(dirname(script), { recursive: true });
|
|
await writeFile(script, "#!/usr/bin/env node\n", { mode: 0o700 });
|
|
await symlink(script, join(version, "bin", "rea"));
|
|
}
|
|
const latest = join(installs, "latest");
|
|
await symlink(old, latest, "dir");
|
|
const unstable = join(latest, scriptWithin("old"));
|
|
const alias = join(latest, "bin", "rea");
|
|
const command = setupRegistrationCommand("linux", undefined, unstable, {
|
|
PATH: join(latest, "bin"),
|
|
});
|
|
expect(command).toEqual([process.execPath, alias, "mcp"]);
|
|
expect(classifyClientRegistrationCommand(command, unstable)).toBe("owned");
|
|
await rm(latest);
|
|
await symlink(next, latest, "dir");
|
|
await rm(old, { recursive: true });
|
|
await expect(access(unstable)).rejects.toMatchObject({ code: "ENOENT" });
|
|
await access(command[1]!);
|
|
expect(await realpath(command[1]!)).toBe(join(next, scriptWithin("next")));
|
|
expect(
|
|
classifyClientRegistrationCommand(
|
|
command,
|
|
join(next, scriptWithin("next")),
|
|
),
|
|
).toBe("owned");
|
|
},
|
|
);
|
|
|
|
it.skipIf(process.platform === "win32").each(["node", "bun"] as const)(
|
|
"preserves a same-file %s runtime alias across a manager version switch",
|
|
async (runtime) => {
|
|
const root = await createTestTempDirectory("rea-stable-runtime-alias-");
|
|
const old = join(root, "old");
|
|
const next = join(root, "next");
|
|
for (const [directory, contents] of [
|
|
[old, "old runtime"],
|
|
[next, "next runtime"],
|
|
] as const) {
|
|
await mkdir(directory);
|
|
await writeFile(join(directory, runtime), contents, { mode: 0o700 });
|
|
}
|
|
const latest = join(root, "latest");
|
|
await symlink(old, latest, "dir");
|
|
const executable = join(old, runtime);
|
|
const alias = join(latest, runtime);
|
|
const selected = registrationRuntimePath(
|
|
executable,
|
|
runtime,
|
|
{ PATH: latest },
|
|
"linux",
|
|
);
|
|
expect(selected).toBe(alias);
|
|
expect(await realpath(selected)).toBe(executable);
|
|
|
|
await rm(latest);
|
|
await symlink(next, latest, "dir");
|
|
await rm(old, { recursive: true });
|
|
await expect(access(executable)).rejects.toMatchObject({ code: "ENOENT" });
|
|
await access(selected);
|
|
expect(await realpath(selected)).toBe(join(next, runtime));
|
|
expect(
|
|
registrationRuntimePath(
|
|
join(next, runtime),
|
|
runtime,
|
|
{ PATH: latest },
|
|
"linux",
|
|
),
|
|
).toBe(selected);
|
|
},
|
|
);
|
|
|
|
it.skipIf(process.platform === "win32")(
|
|
"does not replace the selected runtime with a foreign executable or manager shim",
|
|
async () => {
|
|
const root = await createTestTempDirectory("rea-runtime-alias-identity-");
|
|
const foreign = join(root, "foreign");
|
|
const missing = join(root, "missing");
|
|
const valid = join(root, "valid");
|
|
const runtime = javaScriptRuntime(process.versions).id;
|
|
const script = join(root, "rea.mjs");
|
|
await writeFile(script, "// selected REA script\n");
|
|
for (const directory of [foreign, missing, valid]) await mkdir(directory);
|
|
await writeFile(join(foreign, runtime), '#!/bin/sh\nexec node "$@"\n', {
|
|
mode: 0o700,
|
|
});
|
|
await symlink(join(root, "absent"), join(missing, runtime));
|
|
await symlink(process.execPath, join(valid, runtime));
|
|
expect(
|
|
registrationRuntimePath(
|
|
process.execPath,
|
|
runtime,
|
|
{ PATH: `${foreign}:${missing}:relative` },
|
|
"linux",
|
|
),
|
|
).toBe(process.execPath);
|
|
expect(
|
|
registrationRuntimePath(
|
|
process.execPath,
|
|
runtime,
|
|
{ PATH: `${foreign}:${valid}` },
|
|
"linux",
|
|
),
|
|
).toBe(join(valid, runtime));
|
|
expect(
|
|
setupRegistrationCommand("linux", undefined, script, { PATH: valid }),
|
|
).toEqual([join(valid, runtime), script, "mcp"]);
|
|
expect(
|
|
registrationRuntimePath(
|
|
process.execPath,
|
|
runtime,
|
|
{ PATH: valid },
|
|
"win32",
|
|
),
|
|
).toBe(process.execPath);
|
|
},
|
|
);
|
|
|
|
it.skipIf(process.platform === "win32")(
|
|
"checks the native PATH alias instead of collapsing a symlink parent segment",
|
|
async () => {
|
|
const root = await createTestTempDirectory("rea-runtime-path-parent-");
|
|
const runtime = javaScriptRuntime(process.versions).id;
|
|
await mkdir(join(root, "selected"));
|
|
await mkdir(join(root, "other", "child"), { recursive: true });
|
|
await mkdir(join(root, "other", "selected"));
|
|
await symlink(join(root, "other", "child"), join(root, "link"));
|
|
await symlink(process.execPath, join(root, "selected", runtime));
|
|
await writeFile(
|
|
join(root, "other", "selected", runtime),
|
|
"foreign runtime\n",
|
|
{ mode: 0o700 },
|
|
);
|
|
expect(
|
|
registrationRuntimePath(
|
|
process.execPath,
|
|
runtime,
|
|
{
|
|
PATH: `${root}/link/../selected`,
|
|
},
|
|
process.platform,
|
|
),
|
|
).toBe(process.execPath);
|
|
},
|
|
);
|
|
|
|
it.skipIf(process.platform === "win32")(
|
|
"retains unknown runtime identity after its executable disappears despite a known REA script",
|
|
async () => {
|
|
const home = await createTestTempDirectory("rea-removed-runtime-");
|
|
const executable = join(home, "node");
|
|
const script = join(home, "rea.mjs");
|
|
const config = join(home, ".cursor", "mcp.json");
|
|
await symlink(process.execPath, executable);
|
|
await writeFile(script, "// selected REA script\n");
|
|
await mkdir(join(home, ".cursor"));
|
|
const command = [executable, script, "mcp"];
|
|
const original = JSON.stringify({
|
|
mcpServers: {
|
|
rea: { command: executable, args: [script, "mcp"] },
|
|
},
|
|
});
|
|
await writeFile(config, original);
|
|
const inspect = () =>
|
|
readClientRegistrationInspections(home, script, {
|
|
environment: {},
|
|
selectedClientIds: ["cursor"],
|
|
});
|
|
expect(await inspect()).toContainEqual(
|
|
expect.objectContaining({
|
|
commandIdentity: "owned",
|
|
status: expect.objectContaining({ client: "cursor", state: "aligned" }),
|
|
}),
|
|
);
|
|
await rm(executable);
|
|
expect(classifyClientRegistrationCommand(command, script)).toBe("unknown");
|
|
expect(await inspect()).toContainEqual(
|
|
expect.objectContaining({
|
|
commandIdentity: "unknown",
|
|
status: expect.objectContaining({
|
|
client: "cursor",
|
|
state: "unknown",
|
|
command,
|
|
remediation: expect.any(String),
|
|
}),
|
|
}),
|
|
);
|
|
expect(await readFile(config, "utf8")).toBe(original);
|
|
},
|
|
);
|
|
|
|
it.skipIf(process.platform === "win32")(
|
|
"keeps selected entrypoint when PATH aliases are foreign, unresolved, unexecutable or relative",
|
|
async () => {
|
|
const root = await createTestTempDirectory("rea-unproven-launcher-alias-");
|
|
const entrypoint = join(root, "selected.mjs");
|
|
await writeFile(entrypoint, "selected", { mode: 0o700 });
|
|
const foreign = join(root, "foreign");
|
|
const missing = join(root, "missing");
|
|
const unexecutable = join(root, "unexecutable");
|
|
for (const path of [foreign, missing, unexecutable]) await mkdir(path);
|
|
await writeFile(join(foreign, "rea"), "different program", { mode: 0o700 });
|
|
await symlink(join(root, "absent"), join(missing, "rea"));
|
|
// Same identity cannot be reached via an executable launcher when the selected
|
|
// file itself has no executable bit; no shell wrapper is inferred or run.
|
|
const selected = join(root, "unexecutable.mjs");
|
|
await writeFile(selected, "selected", { mode: 0o600 });
|
|
await symlink(selected, join(unexecutable, "rea"));
|
|
expect(
|
|
setupRegistrationCommand("linux", undefined, entrypoint, {
|
|
PATH: `${foreign}:${missing}:relative`,
|
|
}),
|
|
).toEqual([process.execPath, entrypoint, "mcp"]);
|
|
expect(
|
|
setupRegistrationCommand("linux", undefined, selected, {
|
|
PATH: unexecutable,
|
|
}),
|
|
).toEqual([process.execPath, selected, "mcp"]);
|
|
expect(
|
|
setupRegistrationCommand("win32", undefined, entrypoint, {
|
|
PATH: foreign,
|
|
}),
|
|
).toEqual([process.execPath, entrypoint, "mcp"]);
|
|
},
|
|
);
|