mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-27 04:47:03 -06:00
fix(plugins): invalidate replaced web runtime artifacts (#126867)
This commit is contained in:
committed by
GitHub
parent
c19bdb3a1d
commit
b00734bd4c
@@ -1,7 +1,13 @@
|
||||
// Runtime web-channel plugin tests cover web channel plugin activation and runtime behavior.
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import { createTempDirTracker } from "../../../test/helpers/temp-dir.js";
|
||||
|
||||
const tempDirs = createTempDirTracker();
|
||||
|
||||
afterEach(() => {
|
||||
tempDirs.cleanup();
|
||||
vi.doUnmock("./runtime-plugin-boundary.js");
|
||||
vi.resetModules();
|
||||
});
|
||||
@@ -51,6 +57,57 @@ describe("runtime web channel plugin", () => {
|
||||
expect(resolvePluginRuntimeRecordByEntryBaseNames).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
it.each(["light", "heavy"] as const)(
|
||||
"reloads replaced %s runtime artifacts and dependencies after plugin lifecycle clears",
|
||||
async (kind) => {
|
||||
const pluginRoot = fs.realpathSync(tempDirs.make("openclaw-web-runtime-replacement-"));
|
||||
const modulePath = path.join(
|
||||
pluginRoot,
|
||||
kind === "light" ? "light-runtime-api.js" : "runtime-api.js",
|
||||
);
|
||||
const dependencyPath = path.join(pluginRoot, "dependency.js");
|
||||
fs.writeFileSync(path.join(pluginRoot, "package.json"), '{"type":"commonjs"}\n', "utf8");
|
||||
|
||||
const writeRuntime = (marker: string) => {
|
||||
fs.writeFileSync(dependencyPath, `module.exports = ${JSON.stringify(marker)};\n`, "utf8");
|
||||
const exportName = kind === "light" ? "resolveDefaultWebAuthDir" : "startWebLoginWithQr";
|
||||
fs.writeFileSync(
|
||||
modulePath,
|
||||
`module.exports = { ${exportName}: () => ${JSON.stringify(marker)} + ":" + require("./dependency.js") };\n`,
|
||||
"utf8",
|
||||
);
|
||||
};
|
||||
writeRuntime("retired");
|
||||
|
||||
vi.doMock("./runtime-plugin-boundary.js", async (importOriginal) => ({
|
||||
...(await importOriginal<typeof import("./runtime-plugin-boundary.js")>()),
|
||||
resolvePluginRuntimeRecordByEntryBaseNames: () => ({
|
||||
origin: "global",
|
||||
rootDir: pluginRoot,
|
||||
source: path.join(pluginRoot, "index.js"),
|
||||
}),
|
||||
resolvePluginRuntimeModulePath: () => modulePath,
|
||||
}));
|
||||
|
||||
const runtime = await import("./runtime-web-channel-plugin.js");
|
||||
const { clearPluginMetadataLifecycleCaches } =
|
||||
await import("../plugin-metadata-lifecycle.js");
|
||||
const invoke = () =>
|
||||
kind === "light"
|
||||
? Promise.resolve(runtime.resolveWebChannelAuthDir())
|
||||
: runtime.startWebLoginWithQr();
|
||||
|
||||
await expect(invoke()).resolves.toBe("retired:retired");
|
||||
writeRuntime("replacement");
|
||||
await expect(invoke()).resolves.toBe("retired:retired");
|
||||
|
||||
clearPluginMetadataLifecycleCaches();
|
||||
|
||||
await expect(invoke()).resolves.toBe("replacement:replacement");
|
||||
await expect(invoke()).resolves.toBe("replacement:replacement");
|
||||
},
|
||||
);
|
||||
|
||||
it("reports heavy runtime load failures as promise rejections", async () => {
|
||||
vi.doMock("./runtime-plugin-boundary.js", () => ({
|
||||
loadPluginBoundaryModule: () => {
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
// Runtime web-channel plugin helpers expose web-channel tools through activated plugin runtimes.
|
||||
import path from "node:path";
|
||||
import { getDefaultLocalRootsCore } from "../../media/web-media.js";
|
||||
import { registerPluginMetadataProcessMemoLifecycleClear } from "../plugin-metadata-lifecycle.js";
|
||||
import {
|
||||
clearPluginModuleLoaderLifecycleCache,
|
||||
createPluginModuleLoaderCache,
|
||||
type PluginModuleLoaderCache,
|
||||
} from "../plugin-module-loader-cache.js";
|
||||
@@ -64,10 +66,11 @@ const webChannelRuntimeModuleCache = new Map<
|
||||
>();
|
||||
|
||||
const moduleLoaders: PluginModuleLoaderCache = createPluginModuleLoaderCache();
|
||||
const moduleRoots = new Map<string, string>();
|
||||
|
||||
registerPluginMetadataProcessMemoLifecycleClear(() => {
|
||||
webChannelRuntimeModuleCache.clear();
|
||||
moduleLoaders.clear();
|
||||
clearPluginModuleLoaderLifecycleCache({ moduleLoaders, moduleRoots });
|
||||
});
|
||||
|
||||
/** Resolves the active web-channel plugin record that provides runtime APIs. */
|
||||
@@ -89,6 +92,7 @@ function resolveWebChannelRuntimeModulePath(
|
||||
if (!modulePath) {
|
||||
throw new Error(`web channel plugin runtime is unavailable: missing ${entryBaseName}`);
|
||||
}
|
||||
moduleRoots.set(modulePath, record.rootDir ?? path.dirname(record.source));
|
||||
return modulePath;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user