mirror of
https://github.com/morluto/rea.git
synced 2026-11-15 19:23:24 +00:00
fix(cli): refuse silent direct command-registry execution (#11204)
Fail visibly with a dispatcher hint and exit status 1 when the compiled command registry is executed directly. Reuse the existing argv/module identity owner for both entry modules and preserve normal imports and supported dispatch. Fixes #10762. Builds on #10766. Co-authored-by: 2SinceJuly <202121119+2SinceJuly@users.noreply.github.com> Co-authored-by: gensokyo-dev <342580529+gensokyo-dev@users.noreply.github.com>
This commit is contained in:
5 files changed
+116
-16
No files matched your search
+13
@@ -1,4 +1,8 @@
|
||||
import type { FridaInstrumentationPort } from "./application/frida/FridaInstrumentationPort.js";
|
||||
import {
|
||||
CURRENT_ENTRYPOINT_PATH,
|
||||
isDirectEntrypoint,
|
||||
} from "./process/CurrentEntrypointPath.js";
|
||||
import {
|
||||
CLI_COMMAND_REGISTRATIONS,
|
||||
CLI_REGISTRATION_ORDER,
|
||||
@@ -141,3 +145,12 @@ export const createCli = (
|
||||
module: CLI_COMMAND_MODULES[id],
|
||||
})),
|
||||
});
|
||||
|
||||
// Direct execution needs a visible failure pointing to the dispatcher. Imports
|
||||
// only expose the registry. A normal exit lets piped stderr drain completely.
|
||||
if (isDirectEntrypoint(CURRENT_ENTRYPOINT_PATH, import.meta.url)) {
|
||||
process.stderr.write(
|
||||
"dist/cli.js is the command registry, not the CLI entry point. Run `rea` (scripts/rea.mjs) instead.\n",
|
||||
);
|
||||
process.exitCode = 1;
|
||||
}
|
||||
+4
-16
@@ -2,9 +2,10 @@
|
||||
|
||||
import { snapshotEnvironment } from "./process/snapshotEnvironment.js";
|
||||
import { HOST_PLATFORM } from "./platform/HostPlatform.js";
|
||||
import { CURRENT_ENTRYPOINT_PATH } from "./process/CurrentEntrypointPath.js";
|
||||
import { pathToFileURL } from "node:url";
|
||||
import { realpathSync } from "node:fs";
|
||||
import {
|
||||
CURRENT_ENTRYPOINT_PATH,
|
||||
isDirectEntrypoint,
|
||||
} from "./process/CurrentEntrypointPath.js";
|
||||
|
||||
import { serveStdio } from "@modelcontextprotocol/server/stdio";
|
||||
|
||||
@@ -206,19 +207,6 @@ export const runMcpHttpEntrypoint = async (
|
||||
}
|
||||
};
|
||||
|
||||
/** Return false when the runtime's argv path is synthetic or no longer exists. */
|
||||
const isDirectEntrypoint = (
|
||||
entryPath: string | undefined,
|
||||
moduleUrl: string,
|
||||
): boolean => {
|
||||
if (entryPath === undefined) return false;
|
||||
try {
|
||||
return pathToFileURL(realpathSync(entryPath)).href === moduleUrl;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
if (isDirectEntrypoint(CURRENT_ENTRYPOINT_PATH, import.meta.url)) {
|
||||
void runEntrypoint();
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import { symlink, writeFile } from "node:fs/promises";
|
||||
import { join } from "node:path";
|
||||
import { pathToFileURL } from "node:url";
|
||||
|
||||
import { expect, it } from "vitest";
|
||||
|
||||
import { createTestTempDirectory } from "../../tests/fixtures/temporaryDirectory.js";
|
||||
import { isDirectEntrypoint } from "./CurrentEntrypointPath.js";
|
||||
|
||||
const temporaryScript = async (): Promise<{
|
||||
path: string;
|
||||
url: string;
|
||||
}> => {
|
||||
const directory = await createTestTempDirectory("rea-entrypoint-");
|
||||
const path = join(directory, "module.js");
|
||||
await writeFile(path, "export {};\n");
|
||||
return { path, url: pathToFileURL(path).href };
|
||||
};
|
||||
|
||||
it("matches a symlinked argv path by its real location", async () => {
|
||||
const { path, url } = await temporaryScript();
|
||||
const directory = await createTestTempDirectory("rea-entrypoint-link-");
|
||||
const link = join(directory, "link.js");
|
||||
await symlink(path, link);
|
||||
expect(isDirectEntrypoint(link, url)).toBe(true);
|
||||
});
|
||||
|
||||
it("reports no direct run when no argv path was supplied", async () => {
|
||||
const { url } = await temporaryScript();
|
||||
expect(isDirectEntrypoint(undefined, url)).toBe(false);
|
||||
});
|
||||
|
||||
it("reports no direct run when the argv path no longer exists", async () => {
|
||||
const { url } = await temporaryScript();
|
||||
const directory = await createTestTempDirectory("rea-entrypoint-gone-");
|
||||
expect(isDirectEntrypoint(join(directory, "missing.js"), url)).toBe(false);
|
||||
});
|
||||
@@ -1,4 +1,6 @@
|
||||
import { realpathSync } from "node:fs";
|
||||
import { resolve } from "node:path";
|
||||
import { pathToFileURL } from "node:url";
|
||||
import {
|
||||
javaScriptRuntime,
|
||||
type JavaScriptRuntime,
|
||||
@@ -19,3 +21,18 @@ export const normalizeEntrypointPath = (
|
||||
: resolve(entrypoint);
|
||||
|
||||
export const CURRENT_ENTRYPOINT_PATH = normalizeEntrypointPath(process.argv[1]);
|
||||
|
||||
/** True when the runtime executed `moduleUrl` directly rather than importing it.
|
||||
* A synthetic argv path that no longer resolves is not a direct run.
|
||||
*/
|
||||
export const isDirectEntrypoint = (
|
||||
entryPath: string | undefined,
|
||||
moduleUrl: string,
|
||||
): boolean => {
|
||||
if (entryPath === undefined) return false;
|
||||
try {
|
||||
return pathToFileURL(realpathSync(entryPath)).href === moduleUrl;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,45 @@
|
||||
import { expect } from "vitest";
|
||||
|
||||
import { processTest } from "../../support/process/processFixture.js";
|
||||
|
||||
// Exercise the compiled registry and supported dispatcher; test:focused builds them.
|
||||
processTest.for([[], ["--version"]] as const)(
|
||||
"refuses a direct run of the compiled command registry with %j",
|
||||
async (arguments_, { processes }) => {
|
||||
const direct = await processes.run(process.execPath, [
|
||||
"dist/cli.js",
|
||||
...arguments_,
|
||||
]);
|
||||
expect(direct.exitCode).toBe(1);
|
||||
expect(direct.stdout).toBe("");
|
||||
expect(direct.stderr).toContain("dist/cli.js is the command registry");
|
||||
expect(direct.stderr).toContain("scripts/rea.mjs");
|
||||
},
|
||||
);
|
||||
|
||||
processTest(
|
||||
"still serves --version through the dispatcher",
|
||||
async ({ processes }) => {
|
||||
const dispatched = await processes.run(process.execPath, [
|
||||
"scripts/rea.mjs",
|
||||
"--version",
|
||||
]);
|
||||
expect(dispatched.exitCode).toBe(0);
|
||||
expect(dispatched.stderr).toBe("");
|
||||
expect(dispatched.stdout.trim()).not.toBe("");
|
||||
},
|
||||
);
|
||||
|
||||
processTest(
|
||||
"imports the registry without serving or replacing the caller exit status",
|
||||
async ({ processes }) => {
|
||||
const imported = await processes.run(process.execPath, [
|
||||
"--input-type=module",
|
||||
"--eval",
|
||||
'process.exitCode = 23; const registry = await import("./dist/cli.js"); process.stdout.write(typeof registry.createCli);',
|
||||
]);
|
||||
expect(imported.exitCode).toBe(23);
|
||||
expect(imported.stdout).toBe("function");
|
||||
expect(imported.stderr).toBe("");
|
||||
},
|
||||
);
|
||||
Reference in new issue
Block a user