fix(plugins): avoid duplicate native fallback loads

This commit is contained in:
Vincent Koc
2026-05-04 01:49:14 -07:00
parent 474bea162b
commit 7482754aca
3 changed files with 43 additions and 2 deletions

View File

@@ -61,6 +61,7 @@ Docs: https://docs.openclaw.ai
### Fixes
- Plugins/loader: do not retry native-loaded JavaScript plugin modules through the source transformer after native evaluation has already reached a missing dependency, avoiding duplicate top-level side effects. Thanks @vincentkoc.
- Plugins/packages: reject blank `openclaw.runtimeExtensions` entries instead of silently ignoring them and falling back to inferred TypeScript runtime entries. Thanks @vincentkoc.
- Doctor/plugins: remove stale managed npm plugin shadow entries from the managed package lock as well as `package.json` and `node_modules`, so future npm operations do not keep referencing repaired bundled-plugin shadows. Thanks @vincentkoc.
- Plugins/runtime state: keep the key being registered when namespace eviction runs in the same millisecond as existing entries, so `register` and `registerIfAbsent` do not report success while evicting their own fresh value. Thanks @vincentkoc.

View File

@@ -406,7 +406,6 @@ describe("getCachedPluginModuleLoader", () => {
// allowWindows must be passed so the native fast path works on Windows too.
expect(nativeStub).toHaveBeenCalledWith("/repo/dist/extensions/demo/api.js", {
allowWindows: true,
fallbackOnMissingDependency: true,
});
expect(getPluginModuleLoaderStats()).toMatchObject({
calls: 1,
@@ -417,6 +416,48 @@ describe("getCachedPluginModuleLoader", () => {
});
});
it("does not source-transform fallback after native loading reaches a missing dependency", async () => {
const fromSourceTransformer = vi.fn();
const createJiti = vi.fn(() => fromSourceTransformer);
vi.doMock("jiti", () => ({ createJiti }));
const missingDependency = Object.assign(new Error("Cannot find module 'missing-dep'"), {
code: "MODULE_NOT_FOUND",
});
const nativeStub = vi.fn(() => {
throw missingDependency;
});
vi.doMock("./native-module-require.js", () => ({
isJavaScriptModulePath: () => true,
tryNativeRequireJavaScriptModule: nativeStub,
}));
const { getCachedPluginModuleLoader, getPluginModuleLoaderStats } = await importFreshModule<
typeof import("./plugin-module-loader-cache.js")
>(import.meta.url, "./plugin-module-loader-cache.js?scope=native-missing-dependency");
const cache = new Map();
const loader = getCachedPluginModuleLoader({
cache,
modulePath: "/repo/dist/extensions/demo/api.js",
importerUrl: "file:///repo/src/plugins/public-surface-loader.ts",
loaderFilename: "file:///repo/src/plugins/public-surface-loader.ts",
createLoader: asPluginModuleLoaderFactory(createJiti),
});
expect(() => loader("/repo/dist/extensions/demo/api.js")).toThrow("missing-dep");
expect(createJiti).not.toHaveBeenCalled();
expect(fromSourceTransformer).not.toHaveBeenCalled();
expect(nativeStub).toHaveBeenCalledWith("/repo/dist/extensions/demo/api.js", {
allowWindows: true,
});
expect(getPluginModuleLoaderStats()).toMatchObject({
calls: 1,
nativeHits: 0,
nativeMisses: 0,
sourceTransformFallbacks: 0,
sourceTransformForced: 0,
});
});
it("falls back to source transform when the native-require helper declines", async () => {
const fromSourceTransformer = vi.fn(() => ({ fromSourceTransform: true }));
const createJiti = vi.fn(() => fromSourceTransformer);

View File

@@ -284,7 +284,6 @@ function createPluginModuleLoader(params: {
}
const native = tryNativeRequireJavaScriptModule(target, {
allowWindows: true,
fallbackOnMissingDependency: true,
});
if (native.ok) {
pluginModuleLoaderStats.nativeHits += 1;