mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-03 06:20:20 +00:00
fix: centralize Windows bundled Jiti loader policy (#62286) (thanks @chen-zhang-cs-code)
This commit is contained in:
@@ -6,7 +6,7 @@ import { openBoundaryFileSync } from "../../infra/boundary-file-read.js";
|
||||
import {
|
||||
buildPluginLoaderAliasMap,
|
||||
buildPluginLoaderJitiOptions,
|
||||
shouldPreferNativeJiti,
|
||||
resolvePluginLoaderJitiTryNative,
|
||||
} from "../../plugins/sdk-alias.js";
|
||||
|
||||
const nodeRequire = createRequire(import.meta.url);
|
||||
@@ -15,9 +15,9 @@ function createModuleLoader() {
|
||||
const jitiLoaders = new Map<string, ReturnType<typeof createJiti>>();
|
||||
|
||||
return (modulePath: string) => {
|
||||
const tryNative =
|
||||
shouldPreferNativeJiti(modulePath) ||
|
||||
(process.platform !== "win32" && modulePath.includes(`${path.sep}dist${path.sep}`));
|
||||
const tryNative = resolvePluginLoaderJitiTryNative(modulePath, {
|
||||
preferBuiltDist: true,
|
||||
});
|
||||
const aliasMap = buildPluginLoaderAliasMap(modulePath, process.argv[1], import.meta.url);
|
||||
const cacheKey = JSON.stringify({
|
||||
tryNative,
|
||||
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
buildPluginLoaderAliasMap,
|
||||
buildPluginLoaderJitiOptions,
|
||||
resolveLoaderPackageRoot,
|
||||
shouldPreferNativeJiti,
|
||||
resolvePluginLoaderJitiTryNative,
|
||||
} from "../plugins/sdk-alias.js";
|
||||
import type { AnyAgentTool, OpenClawPluginApi, PluginCommandContext } from "../plugins/types.js";
|
||||
|
||||
@@ -252,9 +252,9 @@ function resolveBundledEntryModulePath(importMetaUrl: string, specifier: string)
|
||||
}
|
||||
|
||||
function getJiti(modulePath: string) {
|
||||
const tryNative =
|
||||
shouldPreferNativeJiti(modulePath) ||
|
||||
(process.platform !== "win32" && modulePath.includes(`${path.sep}dist${path.sep}`));
|
||||
const tryNative = resolvePluginLoaderJitiTryNative(modulePath, {
|
||||
preferBuiltDist: true,
|
||||
});
|
||||
const aliasMap = buildPluginLoaderAliasMap(modulePath, process.argv[1], import.meta.url);
|
||||
const cacheKey = JSON.stringify({
|
||||
tryNative,
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
listImportedBundledPluginFacadeIds,
|
||||
loadBundledPluginPublicSurfaceModuleSync,
|
||||
resetFacadeLoaderStateForTest,
|
||||
setFacadeLoaderJitiFactoryForTest,
|
||||
} from "./facade-loader.js";
|
||||
import { listImportedBundledPluginFacadeIds as listImportedFacadeRuntimeIds } from "./facade-runtime.js";
|
||||
import { createPluginSdkTestHarness } from "./test-helpers.js";
|
||||
@@ -12,6 +13,7 @@ import { createPluginSdkTestHarness } from "./test-helpers.js";
|
||||
const { createTempDirSync } = createPluginSdkTestHarness();
|
||||
const originalBundledPluginsDir = process.env.OPENCLAW_BUNDLED_PLUGINS_DIR;
|
||||
const FACADE_LOADER_GLOBAL = "__openclawTestLoadBundledPluginPublicSurfaceModuleSync";
|
||||
type FacadeLoaderJitiFactory = NonNullable<Parameters<typeof setFacadeLoaderJitiFactoryForTest>[0]>;
|
||||
|
||||
function createBundledPluginDir(prefix: string, marker: string): string {
|
||||
const rootDir = createTempDirSync(prefix);
|
||||
@@ -68,6 +70,7 @@ function createCircularPluginDir(prefix: string): string {
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
resetFacadeLoaderStateForTest();
|
||||
setFacadeLoaderJitiFactoryForTest(undefined);
|
||||
delete (globalThis as typeof globalThis & Record<string, unknown>)[FACADE_LOADER_GLOBAL];
|
||||
if (originalBundledPluginsDir === undefined) {
|
||||
delete process.env.OPENCLAW_BUNDLED_PLUGINS_DIR;
|
||||
@@ -115,6 +118,45 @@ describe("plugin-sdk facade loader", () => {
|
||||
expect(listImportedFacadeRuntimeIds()).toEqual(["demo"]);
|
||||
});
|
||||
|
||||
it("keeps Windows dist facade loads off Jiti native import", () => {
|
||||
const dir = createTempDirSync("openclaw-facade-loader-windows-dist-");
|
||||
const bundledPluginsDir = path.join(dir, "dist");
|
||||
fs.mkdirSync(path.join(bundledPluginsDir, "demo"), { recursive: true });
|
||||
fs.writeFileSync(
|
||||
path.join(bundledPluginsDir, "demo", "api.js"),
|
||||
'export const marker = "windows-dist-ok";\n',
|
||||
"utf8",
|
||||
);
|
||||
process.env.OPENCLAW_BUNDLED_PLUGINS_DIR = bundledPluginsDir;
|
||||
|
||||
const createJitiCalls: Parameters<FacadeLoaderJitiFactory>[] = [];
|
||||
setFacadeLoaderJitiFactoryForTest(((...args) => {
|
||||
createJitiCalls.push(args);
|
||||
return vi.fn(() => ({
|
||||
marker: "windows-dist-ok",
|
||||
})) as unknown as ReturnType<FacadeLoaderJitiFactory>;
|
||||
}) as FacadeLoaderJitiFactory);
|
||||
const platformSpy = vi.spyOn(process, "platform", "get").mockReturnValue("win32");
|
||||
|
||||
try {
|
||||
expect(
|
||||
loadBundledPluginPublicSurfaceModuleSync<{ marker: string }>({
|
||||
dirName: "demo",
|
||||
artifactBasename: "api.js",
|
||||
}).marker,
|
||||
).toBe("windows-dist-ok");
|
||||
expect(createJitiCalls).toHaveLength(1);
|
||||
expect(createJitiCalls[0]?.[0]).toEqual(expect.any(String));
|
||||
expect(createJitiCalls[0]?.[1]).toEqual(
|
||||
expect.objectContaining({
|
||||
tryNative: false,
|
||||
}),
|
||||
);
|
||||
} finally {
|
||||
platformSpy.mockRestore();
|
||||
}
|
||||
});
|
||||
|
||||
it("breaks circular facade re-entry during module evaluation", () => {
|
||||
const dir = createCircularPluginDir("openclaw-facade-loader-circular-");
|
||||
process.env.OPENCLAW_BUNDLED_PLUGINS_DIR = dir;
|
||||
|
||||
@@ -8,8 +8,8 @@ import { resolveBundledPluginPublicSurfacePath } from "../plugins/public-surface
|
||||
import {
|
||||
buildPluginLoaderAliasMap,
|
||||
buildPluginLoaderJitiOptions,
|
||||
resolvePluginLoaderJitiTryNative,
|
||||
resolveLoaderPackageRoot,
|
||||
shouldPreferNativeJiti,
|
||||
} from "../plugins/sdk-alias.js";
|
||||
|
||||
const CURRENT_MODULE_PATH = fileURLToPath(import.meta.url);
|
||||
@@ -137,8 +137,9 @@ function resolveFacadeModuleLocation(params: {
|
||||
}
|
||||
|
||||
function getJiti(modulePath: string) {
|
||||
const tryNative =
|
||||
shouldPreferNativeJiti(modulePath) || modulePath.includes(`${path.sep}dist${path.sep}`);
|
||||
const tryNative = resolvePluginLoaderJitiTryNative(modulePath, {
|
||||
preferBuiltDist: true,
|
||||
});
|
||||
const aliasMap = buildPluginLoaderAliasMap(modulePath, process.argv[1], import.meta.url);
|
||||
const cacheKey = JSON.stringify({
|
||||
tryNative,
|
||||
@@ -306,3 +307,9 @@ export function resetFacadeLoaderStateForTest(): void {
|
||||
facadeLoaderJitiFactory = undefined;
|
||||
cachedOpenClawPackageRoot = undefined;
|
||||
}
|
||||
|
||||
export function setFacadeLoaderJitiFactoryForTest(
|
||||
factory: ((...args: Parameters<(typeof import("jiti"))["createJiti"]>) => JitiLoader) | undefined,
|
||||
): void {
|
||||
facadeLoaderJitiFactory = factory;
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ import type {
|
||||
import {
|
||||
buildPluginLoaderAliasMap,
|
||||
buildPluginLoaderJitiOptions,
|
||||
shouldPreferNativeJiti,
|
||||
resolvePluginLoaderJitiTryNative,
|
||||
} from "./sdk-alias.js";
|
||||
import type { PluginConfigUiHint } from "./types.js";
|
||||
|
||||
@@ -75,8 +75,9 @@ function resolveConfigSchemaExport(imported: Record<string, unknown>): ChannelCo
|
||||
}
|
||||
|
||||
function getJiti(modulePath: string) {
|
||||
const tryNative =
|
||||
shouldPreferNativeJiti(modulePath) || modulePath.includes(`${path.sep}dist${path.sep}`);
|
||||
const tryNative = resolvePluginLoaderJitiTryNative(modulePath, {
|
||||
preferBuiltDist: true,
|
||||
});
|
||||
const aliasMap = buildPluginLoaderAliasMap(modulePath, process.argv[1], import.meta.url);
|
||||
const cacheKey = JSON.stringify({
|
||||
tryNative,
|
||||
|
||||
66
src/plugins/public-surface-loader.test.ts
Normal file
66
src/plugins/public-surface-loader.test.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
import fs from "node:fs";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import { importFreshModule } from "../../test/helpers/import-fresh.ts";
|
||||
|
||||
const tempDirs: string[] = [];
|
||||
const originalBundledPluginsDir = process.env.OPENCLAW_BUNDLED_PLUGINS_DIR;
|
||||
|
||||
function createTempDir(): string {
|
||||
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-public-surface-loader-"));
|
||||
tempDirs.push(tempDir);
|
||||
return tempDir;
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
for (const tempDir of tempDirs.splice(0)) {
|
||||
fs.rmSync(tempDir, { recursive: true, force: true });
|
||||
}
|
||||
vi.restoreAllMocks();
|
||||
vi.resetModules();
|
||||
vi.doUnmock("jiti");
|
||||
if (originalBundledPluginsDir === undefined) {
|
||||
delete process.env.OPENCLAW_BUNDLED_PLUGINS_DIR;
|
||||
} else {
|
||||
process.env.OPENCLAW_BUNDLED_PLUGINS_DIR = originalBundledPluginsDir;
|
||||
}
|
||||
});
|
||||
|
||||
describe("bundled plugin public surface loader", () => {
|
||||
it("keeps Windows dist public artifact loads off Jiti native import", async () => {
|
||||
const createJiti = vi.fn(() => vi.fn(() => ({ marker: "windows-dist-ok" })));
|
||||
vi.doMock("jiti", () => ({
|
||||
createJiti,
|
||||
}));
|
||||
const platformSpy = vi.spyOn(process, "platform", "get").mockReturnValue("win32");
|
||||
|
||||
try {
|
||||
const publicSurfaceLoader = await importFreshModule<
|
||||
typeof import("./public-surface-loader.js")
|
||||
>(import.meta.url, "./public-surface-loader.js?scope=windows-dist-jiti");
|
||||
const tempRoot = createTempDir();
|
||||
const bundledPluginsDir = path.join(tempRoot, "dist");
|
||||
process.env.OPENCLAW_BUNDLED_PLUGINS_DIR = bundledPluginsDir;
|
||||
|
||||
const modulePath = path.join(bundledPluginsDir, "demo", "provider-policy-api.js");
|
||||
fs.mkdirSync(path.dirname(modulePath), { recursive: true });
|
||||
fs.writeFileSync(modulePath, 'export const marker = "windows-dist-ok";\n', "utf8");
|
||||
|
||||
expect(
|
||||
publicSurfaceLoader.loadBundledPluginPublicArtifactModuleSync<{ marker: string }>({
|
||||
dirName: "demo",
|
||||
artifactBasename: "provider-policy-api.js",
|
||||
}).marker,
|
||||
).toBe("windows-dist-ok");
|
||||
expect(createJiti).toHaveBeenCalledWith(
|
||||
expect.any(String),
|
||||
expect.objectContaining({
|
||||
tryNative: false,
|
||||
}),
|
||||
);
|
||||
} finally {
|
||||
platformSpy.mockRestore();
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -8,8 +8,8 @@ import { resolveBundledPluginPublicSurfacePath } from "./public-surface-runtime.
|
||||
import {
|
||||
buildPluginLoaderAliasMap,
|
||||
buildPluginLoaderJitiOptions,
|
||||
resolvePluginLoaderJitiTryNative,
|
||||
resolveLoaderPackageRoot,
|
||||
shouldPreferNativeJiti,
|
||||
} from "./sdk-alias.js";
|
||||
|
||||
const OPENCLAW_PACKAGE_ROOT =
|
||||
@@ -70,8 +70,9 @@ function resolvePublicSurfaceLocation(params: {
|
||||
}
|
||||
|
||||
function getJiti(modulePath: string) {
|
||||
const tryNative =
|
||||
shouldPreferNativeJiti(modulePath) || modulePath.includes(`${path.sep}dist${path.sep}`);
|
||||
const tryNative = resolvePluginLoaderJitiTryNative(modulePath, {
|
||||
preferBuiltDist: true,
|
||||
});
|
||||
const sharedLoader = getSharedBundledPublicSurfaceJiti(modulePath, tryNative);
|
||||
if (sharedLoader) {
|
||||
return sharedLoader;
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
listPluginSdkAliasCandidates,
|
||||
listPluginSdkExportedSubpaths,
|
||||
normalizeJitiAliasTargetPath,
|
||||
resolvePluginLoaderJitiTryNative,
|
||||
resolveExtensionApiAlias,
|
||||
resolvePluginRuntimeModulePath,
|
||||
resolvePluginSdkAliasFile,
|
||||
@@ -692,6 +693,45 @@ describe("plugin sdk alias helpers", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("keeps plugin loader dist shortcuts off on Windows", () => {
|
||||
const originalPlatform = process.platform;
|
||||
Object.defineProperty(process, "platform", {
|
||||
configurable: true,
|
||||
value: "win32",
|
||||
});
|
||||
|
||||
try {
|
||||
expect(
|
||||
resolvePluginLoaderJitiTryNative(`/repo/${bundledDistPluginFile("browser", "index.js")}`, {
|
||||
preferBuiltDist: true,
|
||||
}),
|
||||
).toBe(false);
|
||||
expect(
|
||||
resolvePluginLoaderJitiTryNative(`/repo/${bundledDistPluginFile("browser", "helper.ts")}`, {
|
||||
preferBuiltDist: true,
|
||||
}),
|
||||
).toBe(false);
|
||||
} finally {
|
||||
Object.defineProperty(process, "platform", {
|
||||
configurable: true,
|
||||
value: originalPlatform,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
it("allows plugin loader dist shortcuts on non-Windows hosts", () => {
|
||||
expect(
|
||||
resolvePluginLoaderJitiTryNative(`/repo/${bundledDistPluginFile("browser", "index.js")}`, {
|
||||
preferBuiltDist: true,
|
||||
}),
|
||||
).toBe(true);
|
||||
expect(
|
||||
resolvePluginLoaderJitiTryNative(`/repo/${bundledDistPluginFile("browser", "helper.ts")}`, {
|
||||
preferBuiltDist: true,
|
||||
}),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it("normalizes Windows alias targets before handing them to Jiti", () => {
|
||||
const originalPlatform = process.platform;
|
||||
Object.defineProperty(process, "platform", {
|
||||
|
||||
@@ -441,12 +441,13 @@ export function buildPluginLoaderJitiOptions(aliasMap: Record<string, string>) {
|
||||
};
|
||||
}
|
||||
|
||||
export function shouldPreferNativeJiti(modulePath: string): boolean {
|
||||
function isNativeJitiDisabledByRuntime(): boolean {
|
||||
const versions = process.versions as { bun?: string };
|
||||
if (typeof versions.bun === "string") {
|
||||
return false;
|
||||
}
|
||||
if (process.platform === "win32") {
|
||||
return typeof versions.bun === "string" || process.platform === "win32";
|
||||
}
|
||||
|
||||
export function shouldPreferNativeJiti(modulePath: string): boolean {
|
||||
if (isNativeJitiDisabledByRuntime()) {
|
||||
return false;
|
||||
}
|
||||
switch (path.extname(modulePath).toLowerCase()) {
|
||||
@@ -459,3 +460,18 @@ export function shouldPreferNativeJiti(modulePath: string): boolean {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export function resolvePluginLoaderJitiTryNative(
|
||||
modulePath: string,
|
||||
options?: {
|
||||
preferBuiltDist?: boolean;
|
||||
},
|
||||
): boolean {
|
||||
if (isNativeJitiDisabledByRuntime()) {
|
||||
return false;
|
||||
}
|
||||
return (
|
||||
shouldPreferNativeJiti(modulePath) ||
|
||||
(options?.preferBuiltDist === true && modulePath.includes(`${path.sep}dist${path.sep}`))
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user