fix: centralize Windows bundled Jiti loader policy (#62286) (thanks @chen-zhang-cs-code)

This commit is contained in:
Peter Steinberger
2026-04-07 13:06:28 +01:00
parent 9a6a1508c1
commit de3f742221
10 changed files with 196 additions and 22 deletions

View File

@@ -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,

View File

@@ -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,

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -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,

View 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();
}
});
});

View File

@@ -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;

View File

@@ -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", {

View File

@@ -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}`))
);
}