fix: restrict dev tsx loader to repo-local plugins (Windows external plugin workers)

This commit is contained in:
siyu 2026-08-16 18:41:13 +08:00
parent 50a140ae20
commit 57bfea70e7
2 changed files with 50 additions and 6 deletions

View File

@ -0,0 +1,33 @@
import { describe, expect, it } from "vitest";
import path from "node:path";
import { isRepoLocalPluginPath, REPO_ROOT } from "../services/plugin-loader.js";
describe("isRepoLocalPluginPath", () => {
it("returns true for a package inside the repository", () => {
const pkgPath = path.join(REPO_ROOT, "packages", "plugins", "examples", "plugin-hello-world-example");
expect(isRepoLocalPluginPath(pkgPath)).toBe(true);
});
it("returns true when the package path equals the repository root", () => {
expect(isRepoLocalPluginPath(REPO_ROOT)).toBe(true);
});
it("returns false for a package outside the repository", () => {
const outside = path.join(REPO_ROOT, "..", "external-plugin");
expect(isRepoLocalPluginPath(outside)).toBe(false);
});
it("returns false for a sibling directory that merely shares the repo name prefix", () => {
const sibling = path.join(path.dirname(REPO_ROOT), path.basename(REPO_ROOT) + "-external");
expect(isRepoLocalPluginPath(sibling)).toBe(false);
});
it("returns false for null and undefined", () => {
expect(isRepoLocalPluginPath(null)).toBe(false);
expect(isRepoLocalPluginPath(undefined)).toBe(false);
});
it("returns false for an empty string", () => {
expect(isRepoLocalPluginPath("")).toBe(false);
});
});

View File

@ -703,6 +703,19 @@ function isPathWithin(root: string, target: string): boolean {
return relativePath === "" || (!relativePath.startsWith("..") && !path.isAbsolute(relativePath));
}
/**
* True when a plugin package path is inside this repository.
*
* The dev tsx loader is only applied to repo-local packages, because
* external plugins ship prebuilt JS and tsx's `--import` loader crashes
* on Windows when the worker entry is an absolute drive-letter path
* (ERR_UNSUPPORTED_ESM_URL_SCHEME, protocol 'e:').
*/
export function isRepoLocalPluginPath(packagePath: string | null | undefined): boolean {
if (!packagePath) return false;
return isPathWithin(REPO_ROOT, path.resolve(packagePath));
}
export function isRepoBundledPluginPath(
packageRoot: string,
options: { repoRoot?: string } = {},
@ -2295,18 +2308,16 @@ export function pluginLoader(
// entry as a raw drive-letter path (e.g. `E:\plugins\dist\worker.js`)
// and fails with ERR_UNSUPPORTED_ESM_URL_SCHEME (protocol 'e:').
// External plugins ship prebuilt JS and do not need the tsx loader.
const PLUGIN_REPO_ROOT = path.resolve(__dirname, "../../..");
const resolvedPkgPath = activePlugin.packagePath
? path.resolve(activePlugin.packagePath)
: null;
const isRepoLocalPlugin =
if (
resolvedPkgPath !== null &&
(resolvedPkgPath === PLUGIN_REPO_ROOT ||
resolvedPkgPath.startsWith(PLUGIN_REPO_ROOT + path.sep));
if (isRepoLocalPlugin && existsSync(DEV_TSX_LOADER_PATH)) {
isRepoLocalPluginPath(resolvedPkgPath) &&
existsSync(DEV_TSX_LOADER_PATH)
) {
workerOptions.execArgv = ["--import", DEV_TSX_LOADER_PATH];
}
await workerManager.startWorker(pluginId, workerOptions);
registered.worker = true;