mirror of
https://github.com/morluto/rea.git
synced 2026-11-14 18:52:48 +00:00
fix(session): select the registered entrypoint as the running runtime does
Node resolves its script entrypoint lexically, so a registered spelling that crosses a symlink before `..` names the running file lexically, while Bun follows native traversal (#8844). Select the expected server path with the shared runtime-aware entrypoint normalizer before comparing it with the running entrypoint, and fall back to file identity only when they differ. Co-Authored-By: gensokyo-dev <342580529+gensokyo-dev@users.noreply.github.com>
This commit is contained in:
1 parent
982ea7fa9f
commit
065fbec4b3
2 files changed
+41
-5
No files matched your search
+20
-5
@@ -2,6 +2,12 @@ import { statSync } from "node:fs";
|
||||
import { dirname, basename, resolve } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
import {
|
||||
javaScriptRuntime,
|
||||
type JavaScriptRuntime,
|
||||
} from "./domain/runtimeVersion.js";
|
||||
import { normalizeEntrypointPath } from "./process/CurrentEntrypointPath.js";
|
||||
|
||||
import type { createCatalogIdentity } from "./catalogIdentity.js";
|
||||
import { PRODUCT_IDENTITY, SDK_IDENTITY } from "./identity.js";
|
||||
|
||||
@@ -28,12 +34,15 @@ const packageRoot = (): string => {
|
||||
const compareServerPaths = (
|
||||
expected: string,
|
||||
observed: string,
|
||||
runtime: JavaScriptRuntime["id"],
|
||||
): "aligned" | "mismatch" | "unknown" => {
|
||||
if (expected === observed) return "aligned";
|
||||
if (process.platform === "win32")
|
||||
return resolve(expected) === observed ? "aligned" : "mismatch";
|
||||
// Select the registered script the way this runtime does: Node resolves it
|
||||
// lexically, while Bun follows native traversal through a symlink before `..`.
|
||||
const script = normalizeEntrypointPath(expected, runtime) ?? expected;
|
||||
if (script === observed) return "aligned";
|
||||
if (process.platform === "win32") return "mismatch";
|
||||
try {
|
||||
const selected = statSync(expected, { bigint: true });
|
||||
const selected = statSync(script, { bigint: true });
|
||||
const running = statSync(observed, { bigint: true });
|
||||
return selected.dev === running.dev && selected.ino === running.ino
|
||||
? "aligned"
|
||||
@@ -51,13 +60,19 @@ export const createServerIdentity = (input: {
|
||||
readonly client?: ConnectedClientIdentity;
|
||||
readonly protocolVersion?: string | undefined;
|
||||
readonly catalog: ReturnType<typeof createCatalogIdentity>;
|
||||
/** The runtime that selected `commandPath`; defaults to the current one. */
|
||||
readonly runtime?: JavaScriptRuntime["id"];
|
||||
}) => {
|
||||
const catalog = input.catalog;
|
||||
const serverPath = input.commandPath || undefined;
|
||||
const pathComparison =
|
||||
input.expected?.server_path === undefined || serverPath === undefined
|
||||
? "unknown"
|
||||
: compareServerPaths(input.expected.server_path, serverPath);
|
||||
: compareServerPaths(
|
||||
input.expected.server_path,
|
||||
serverPath,
|
||||
input.runtime ?? javaScriptRuntime(process.versions).id,
|
||||
);
|
||||
const reasons: string[] = [];
|
||||
if (
|
||||
input.expected?.package_version !== undefined &&
|
||||
|
||||
@@ -259,12 +259,14 @@ it.skipIf(process.platform === "win32")(
|
||||
await writeFile(other, "other entrypoint");
|
||||
await symlink(scripts, join(scratch, "link"));
|
||||
const selected = `${scratch}/link/../scripts/rea.mjs`;
|
||||
// Bun selects a script through native traversal (#8844).
|
||||
const identity = (server_path: string) =>
|
||||
createServerIdentity({
|
||||
catalog: DEFAULT_CATALOG_IDENTITY,
|
||||
startedAt: "2026-07-13T00:00:00.000Z",
|
||||
commandPath: canonical,
|
||||
expected: { server_path },
|
||||
runtime: "bun",
|
||||
});
|
||||
|
||||
expect(identity(selected).alignment).toMatchObject({
|
||||
@@ -279,6 +281,25 @@ it.skipIf(process.platform === "win32")(
|
||||
state: "unknown",
|
||||
reasons: [],
|
||||
});
|
||||
// Node resolves its entrypoint lexically, so this spelling runs
|
||||
// scratch/rea.mjs even though traversal through the link leaves scratch.
|
||||
// Bun traverses the link and runs a different file.
|
||||
const lexical = join(scratch, "rea.mjs");
|
||||
await writeFile(lexical, "lexical entrypoint");
|
||||
await writeFile(join(root, "package", "rea.mjs"), "traversed entrypoint");
|
||||
const crossing = (runtime: "node" | "bun") =>
|
||||
createServerIdentity({
|
||||
catalog: DEFAULT_CATALOG_IDENTITY,
|
||||
startedAt: "2026-07-13T00:00:00.000Z",
|
||||
commandPath: lexical,
|
||||
expected: { server_path: `${scratch}/link/../rea.mjs` },
|
||||
runtime,
|
||||
}).alignment;
|
||||
expect(crossing("node")).toMatchObject({ state: "aligned", reasons: [] });
|
||||
expect(crossing("bun")).toMatchObject({
|
||||
state: "mcp_server_restart_required",
|
||||
reasons: ["registration_path_mismatch"],
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
Reference in new issue
Block a user