mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 12:10:42 +00:00
refactor(plugins): keep bundled runtime boundaries native
This commit is contained in:
@@ -4,7 +4,7 @@ import { bundledDistPluginFile } from "openclaw/plugin-sdk/test-fixtures";
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import { stageBundledPluginRuntime } from "../../scripts/stage-bundled-plugin-runtime.mjs";
|
||||
import type { PluginJitiLoaderCache } from "./jiti-loader-cache.js";
|
||||
import { loadPluginBoundaryModuleWithJiti } from "./runtime/runtime-plugin-boundary.js";
|
||||
import { loadPluginBoundaryModule } from "./runtime/runtime-plugin-boundary.js";
|
||||
import { cleanupTrackedTempDirs, makeTrackedTempDir } from "./test-helpers/fs-fixtures.js";
|
||||
|
||||
type LightModule = {
|
||||
@@ -92,13 +92,15 @@ function createBundledWhatsAppRuntimeFixture() {
|
||||
function loadWhatsAppBoundaryModules(runtimePluginDir: string) {
|
||||
const loaders: PluginJitiLoaderCache = new Map();
|
||||
return {
|
||||
light: loadPluginBoundaryModuleWithJiti<LightModule>(
|
||||
light: loadPluginBoundaryModule<LightModule>(
|
||||
path.join(runtimePluginDir, "light-runtime-api.js"),
|
||||
loaders,
|
||||
{ origin: "bundled" },
|
||||
),
|
||||
heavy: loadPluginBoundaryModuleWithJiti<HeavyModule>(
|
||||
heavy: loadPluginBoundaryModule<HeavyModule>(
|
||||
path.join(runtimePluginDir, "runtime-api.js"),
|
||||
loaders,
|
||||
{ origin: "bundled" },
|
||||
),
|
||||
};
|
||||
}
|
||||
@@ -126,4 +128,28 @@ describe("runtime plugin boundary whatsapp seam", () => {
|
||||
it("shares listener state between staged light and heavy runtime modules", () => {
|
||||
expectSharedWhatsAppListenerState(createBundledWhatsAppRuntimeFixture(), "work");
|
||||
});
|
||||
|
||||
it("rejects bundled TypeScript runtime modules instead of using the source loader", () => {
|
||||
const rootDir = makeTrackedTempDir("openclaw-bundled-boundary-ts", tempDirs);
|
||||
const modulePath = path.join(rootDir, "runtime-api.ts");
|
||||
writeRuntimeFixtureText(rootDir, "runtime-api.ts", "export const ok = true;\n");
|
||||
const loaders: PluginJitiLoaderCache = new Map();
|
||||
|
||||
expect(() =>
|
||||
loadPluginBoundaryModule<{ ok: boolean }>(modulePath, loaders, { origin: "bundled" }),
|
||||
).toThrow(/must be built JavaScript/u);
|
||||
expect(loaders.size).toBe(0);
|
||||
});
|
||||
|
||||
it("keeps TypeScript source fallback available for non-bundled plugins", () => {
|
||||
const rootDir = makeTrackedTempDir("openclaw-external-boundary-ts", tempDirs);
|
||||
const modulePath = path.join(rootDir, "runtime-api.ts");
|
||||
writeRuntimeFixtureText(rootDir, "runtime-api.ts", "export const ok = true;\n");
|
||||
const loaders: PluginJitiLoaderCache = new Map();
|
||||
|
||||
expect(
|
||||
loadPluginBoundaryModule<{ ok: boolean }>(modulePath, loaders, { origin: "workspace" }),
|
||||
).toMatchObject({ ok: true });
|
||||
expect(loaders.size).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3,10 +3,14 @@ import path from "node:path";
|
||||
import { getRuntimeConfig } from "../../config/config.js";
|
||||
import { getCachedPluginJitiLoader, type PluginJitiLoaderCache } from "../jiti-loader-cache.js";
|
||||
import { loadPluginManifestRegistry } from "../manifest-registry.js";
|
||||
import { shouldPreferNativeJiti } from "../sdk-alias.js";
|
||||
import {
|
||||
isJavaScriptModulePath,
|
||||
tryNativeRequireJavaScriptModule,
|
||||
} from "../native-module-require.js";
|
||||
import type { PluginOrigin } from "../plugin-origin.types.js";
|
||||
|
||||
type PluginRuntimeRecord = {
|
||||
origin?: string;
|
||||
origin?: PluginOrigin;
|
||||
rootDir?: string;
|
||||
source: string;
|
||||
};
|
||||
@@ -105,21 +109,33 @@ export function resolvePluginRuntimeModulePath(
|
||||
return null;
|
||||
}
|
||||
|
||||
export function getPluginBoundaryJiti(modulePath: string, loaders: PluginJitiLoaderCache) {
|
||||
const tryNative = shouldPreferNativeJiti(modulePath);
|
||||
function getPluginBoundarySourceLoader(modulePath: string, loaders: PluginJitiLoaderCache) {
|
||||
return getCachedPluginJitiLoader({
|
||||
cache: loaders,
|
||||
modulePath,
|
||||
importerUrl: import.meta.url,
|
||||
jitiFilename: import.meta.url,
|
||||
tryNative,
|
||||
tryNative: false,
|
||||
});
|
||||
}
|
||||
|
||||
// oxlint-disable-next-line typescript/no-unnecessary-type-parameters -- Dynamic plugin boundary loaders use caller-supplied module types.
|
||||
export function loadPluginBoundaryModuleWithJiti<TModule>(
|
||||
export function loadPluginBoundaryModule<TModule>(
|
||||
modulePath: string,
|
||||
loaders: PluginJitiLoaderCache,
|
||||
options: { origin?: PluginOrigin } = {},
|
||||
): TModule {
|
||||
return getPluginBoundaryJiti(modulePath, loaders)(modulePath) as TModule;
|
||||
if (isJavaScriptModulePath(modulePath)) {
|
||||
const native = tryNativeRequireJavaScriptModule(modulePath, { allowWindows: true });
|
||||
if (native.ok) {
|
||||
return native.moduleExport as TModule;
|
||||
}
|
||||
if (options.origin === "bundled") {
|
||||
throw new Error(`bundled plugin runtime module must load natively: ${modulePath}`);
|
||||
}
|
||||
} else if (options.origin === "bundled") {
|
||||
throw new Error(`bundled plugin runtime module must be built JavaScript: ${modulePath}`);
|
||||
}
|
||||
|
||||
return getPluginBoundarySourceLoader(modulePath, loaders)(modulePath) as TModule;
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
import type { PollInput } from "../../polls.js";
|
||||
import type { PluginJitiLoaderCache } from "../jiti-loader-cache.js";
|
||||
import {
|
||||
loadPluginBoundaryModuleWithJiti,
|
||||
loadPluginBoundaryModule,
|
||||
resolvePluginRuntimeRecordByEntryBaseNames,
|
||||
resolvePluginRuntimeModulePath,
|
||||
} from "./runtime-plugin-boundary.js";
|
||||
@@ -132,25 +132,22 @@ function resolveWebChannelRuntimeModulePath(
|
||||
}
|
||||
|
||||
function loadCurrentHeavyModuleSync(): WebChannelHeavyRuntimeModule {
|
||||
const modulePath = resolveWebChannelRuntimeModulePath(
|
||||
resolveWebChannelPluginRecord(),
|
||||
"runtime-api",
|
||||
);
|
||||
return loadPluginBoundaryModuleWithJiti<WebChannelHeavyRuntimeModule>(modulePath, jitiLoaders);
|
||||
const record = resolveWebChannelPluginRecord();
|
||||
const modulePath = resolveWebChannelRuntimeModulePath(record, "runtime-api");
|
||||
return loadPluginBoundaryModule<WebChannelHeavyRuntimeModule>(modulePath, jitiLoaders, {
|
||||
origin: record.origin,
|
||||
});
|
||||
}
|
||||
|
||||
function loadWebChannelLightModule(): WebChannelLightRuntimeModule {
|
||||
const modulePath = resolveWebChannelRuntimeModulePath(
|
||||
resolveWebChannelPluginRecord(),
|
||||
"light-runtime-api",
|
||||
);
|
||||
const record = resolveWebChannelPluginRecord();
|
||||
const modulePath = resolveWebChannelRuntimeModulePath(record, "light-runtime-api");
|
||||
if (cachedLightModule && cachedLightModulePath === modulePath) {
|
||||
return cachedLightModule;
|
||||
}
|
||||
const loaded = loadPluginBoundaryModuleWithJiti<WebChannelLightRuntimeModule>(
|
||||
modulePath,
|
||||
jitiLoaders,
|
||||
);
|
||||
const loaded = loadPluginBoundaryModule<WebChannelLightRuntimeModule>(modulePath, jitiLoaders, {
|
||||
origin: record.origin,
|
||||
});
|
||||
cachedLightModulePath = modulePath;
|
||||
cachedLightModule = loaded;
|
||||
return loaded;
|
||||
@@ -162,10 +159,9 @@ async function loadWebChannelHeavyModule(): Promise<WebChannelHeavyRuntimeModule
|
||||
if (cachedHeavyModule && cachedHeavyModulePath === modulePath) {
|
||||
return cachedHeavyModule;
|
||||
}
|
||||
const loaded = loadPluginBoundaryModuleWithJiti<WebChannelHeavyRuntimeModule>(
|
||||
modulePath,
|
||||
jitiLoaders,
|
||||
);
|
||||
const loaded = loadPluginBoundaryModule<WebChannelHeavyRuntimeModule>(modulePath, jitiLoaders, {
|
||||
origin: record.origin,
|
||||
});
|
||||
cachedHeavyModulePath = modulePath;
|
||||
cachedHeavyModule = loaded;
|
||||
return loaded;
|
||||
|
||||
Reference in New Issue
Block a user