This commit is contained in:
siyu1011 2026-09-13 14:18:52 +08:00 committed by GitHub
commit 5311311d35
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 62 additions and 5 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 } = {},
@ -2325,13 +2338,24 @@ export function pluginLoader(
proactiveCompanyScopes: configRows.map((row) => row.companyId),
};
// Repo-local plugin installs can resolve workspace TS sources at runtime
// (for example @paperclipai/shared exports). Run those workers through
// the tsx loader so first-party example plugins work in development.
if (activePlugin.packagePath && existsSync(DEV_TSX_LOADER_PATH)) {
// The tsx loader is only needed for repo-local packages that may
// import workspace TypeScript sources (e.g. @paperclipai/shared)
// at runtime. Restricting it to packages inside this repo also fixes
// a Windows crash for external local-path plugin installs: with
// `--import <tsx-loader>`, Node's ESM loader receives the worker
// 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 resolvedPkgPath = activePlugin.packagePath
? path.resolve(activePlugin.packagePath)
: null;
if (
resolvedPkgPath !== null &&
isRepoLocalPluginPath(resolvedPkgPath) &&
existsSync(DEV_TSX_LOADER_PATH)
) {
workerOptions.execArgv = ["--import", DEV_TSX_LOADER_PATH];
}
await workerManager.startWorker(pluginId, workerOptions);
registered.worker = true;