mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-12 07:20:45 +00:00
fix(plugin-sdk): lazily load legacy root alias
This commit is contained in:
@@ -108,26 +108,94 @@ const fastExports = {
|
||||
resolveControlCommandGate,
|
||||
};
|
||||
|
||||
const monolithic = tryLoadMonolithicSdk();
|
||||
const rootExports =
|
||||
monolithic && typeof monolithic === "object"
|
||||
? {
|
||||
...monolithic,
|
||||
...fastExports,
|
||||
}
|
||||
: { ...fastExports };
|
||||
const target = { ...fastExports };
|
||||
let rootExports = null;
|
||||
|
||||
Object.defineProperty(rootExports, "__esModule", {
|
||||
function getMonolithicSdk() {
|
||||
const loaded = tryLoadMonolithicSdk();
|
||||
if (loaded && typeof loaded === "object") {
|
||||
return loaded;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function getExportValue(prop) {
|
||||
if (Reflect.has(target, prop)) {
|
||||
return Reflect.get(target, prop);
|
||||
}
|
||||
const monolithic = getMonolithicSdk();
|
||||
if (!monolithic) {
|
||||
return undefined;
|
||||
}
|
||||
return Reflect.get(monolithic, prop);
|
||||
}
|
||||
|
||||
function getExportDescriptor(prop) {
|
||||
const ownDescriptor = Reflect.getOwnPropertyDescriptor(target, prop);
|
||||
if (ownDescriptor) {
|
||||
return ownDescriptor;
|
||||
}
|
||||
|
||||
const monolithic = getMonolithicSdk();
|
||||
if (!monolithic) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const descriptor = Reflect.getOwnPropertyDescriptor(monolithic, prop);
|
||||
if (!descriptor) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Proxy invariants require descriptors returned for dynamic properties to be configurable.
|
||||
return {
|
||||
...descriptor,
|
||||
configurable: true,
|
||||
};
|
||||
}
|
||||
|
||||
rootExports = new Proxy(target, {
|
||||
get(_target, prop, receiver) {
|
||||
if (Reflect.has(target, prop)) {
|
||||
return Reflect.get(target, prop, receiver);
|
||||
}
|
||||
return getExportValue(prop);
|
||||
},
|
||||
has(_target, prop) {
|
||||
if (Reflect.has(target, prop)) {
|
||||
return true;
|
||||
}
|
||||
const monolithic = getMonolithicSdk();
|
||||
return monolithic ? Reflect.has(monolithic, prop) : false;
|
||||
},
|
||||
ownKeys() {
|
||||
const keys = new Set(Reflect.ownKeys(target));
|
||||
const monolithic = getMonolithicSdk();
|
||||
if (monolithic) {
|
||||
for (const key of Reflect.ownKeys(monolithic)) {
|
||||
if (!keys.has(key)) {
|
||||
keys.add(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
return [...keys];
|
||||
},
|
||||
getOwnPropertyDescriptor(_target, prop) {
|
||||
return getExportDescriptor(prop);
|
||||
},
|
||||
});
|
||||
|
||||
Object.defineProperty(target, "__esModule", {
|
||||
configurable: true,
|
||||
enumerable: false,
|
||||
writable: false,
|
||||
value: true,
|
||||
});
|
||||
Object.defineProperty(rootExports, "default", {
|
||||
Object.defineProperty(target, "default", {
|
||||
configurable: true,
|
||||
enumerable: false,
|
||||
writable: false,
|
||||
value: rootExports,
|
||||
get() {
|
||||
return rootExports;
|
||||
},
|
||||
});
|
||||
|
||||
module.exports = rootExports;
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
import fs from "node:fs";
|
||||
import { createRequire } from "node:module";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import vm from "node:vm";
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
const require = createRequire(import.meta.url);
|
||||
const rootSdk = require("./root-alias.cjs") as Record<string, unknown>;
|
||||
const rootAliasPath = fileURLToPath(new URL("./root-alias.cjs", import.meta.url));
|
||||
const rootAliasSource = fs.readFileSync(rootAliasPath, "utf-8");
|
||||
|
||||
type EmptySchema = {
|
||||
safeParse: (value: unknown) =>
|
||||
@@ -13,6 +19,64 @@ type EmptySchema = {
|
||||
};
|
||||
};
|
||||
|
||||
function loadRootAliasWithStubs(options?: {
|
||||
distExists?: boolean;
|
||||
monolithicExports?: Record<string | symbol, unknown>;
|
||||
}) {
|
||||
let createJitiCalls = 0;
|
||||
let jitiLoadCalls = 0;
|
||||
const loadedSpecifiers: string[] = [];
|
||||
const monolithicExports = options?.monolithicExports ?? {
|
||||
slowHelper: () => "loaded",
|
||||
};
|
||||
const wrapper = vm.runInNewContext(
|
||||
`(function (exports, require, module, __filename, __dirname) {${rootAliasSource}\n})`,
|
||||
{},
|
||||
{ filename: rootAliasPath },
|
||||
) as (
|
||||
exports: Record<string, unknown>,
|
||||
require: NodeJS.Require,
|
||||
module: { exports: Record<string, unknown> },
|
||||
__filename: string,
|
||||
__dirname: string,
|
||||
) => void;
|
||||
const module = { exports: {} as Record<string, unknown> };
|
||||
const localRequire = ((id: string) => {
|
||||
if (id === "node:path") {
|
||||
return path;
|
||||
}
|
||||
if (id === "node:fs") {
|
||||
return {
|
||||
existsSync: () => options?.distExists ?? false,
|
||||
};
|
||||
}
|
||||
if (id === "jiti") {
|
||||
return {
|
||||
createJiti() {
|
||||
createJitiCalls += 1;
|
||||
return (specifier: string) => {
|
||||
jitiLoadCalls += 1;
|
||||
loadedSpecifiers.push(specifier);
|
||||
return monolithicExports;
|
||||
};
|
||||
},
|
||||
};
|
||||
}
|
||||
throw new Error(`unexpected require: ${id}`);
|
||||
}) as NodeJS.Require;
|
||||
wrapper(module.exports, localRequire, module, rootAliasPath, path.dirname(rootAliasPath));
|
||||
return {
|
||||
moduleExports: module.exports,
|
||||
get createJitiCalls() {
|
||||
return createJitiCalls;
|
||||
},
|
||||
get jitiLoadCalls() {
|
||||
return jitiLoadCalls;
|
||||
},
|
||||
loadedSpecifiers,
|
||||
};
|
||||
}
|
||||
|
||||
describe("plugin-sdk root alias", () => {
|
||||
it("exposes the fast empty config schema helper", () => {
|
||||
const factory = rootSdk.emptyPluginConfigSchema as (() => EmptySchema) | undefined;
|
||||
@@ -27,6 +91,36 @@ describe("plugin-sdk root alias", () => {
|
||||
expect(parsed.success).toBe(false);
|
||||
});
|
||||
|
||||
it("does not load the monolithic sdk for fast helpers", () => {
|
||||
const lazyModule = loadRootAliasWithStubs();
|
||||
const lazyRootSdk = lazyModule.moduleExports;
|
||||
const factory = lazyRootSdk.emptyPluginConfigSchema as (() => EmptySchema) | undefined;
|
||||
|
||||
expect(lazyModule.createJitiCalls).toBe(0);
|
||||
expect(lazyModule.jitiLoadCalls).toBe(0);
|
||||
expect(typeof factory).toBe("function");
|
||||
expect(factory?.().safeParse({})).toEqual({ success: true, data: {} });
|
||||
expect(lazyModule.createJitiCalls).toBe(0);
|
||||
expect(lazyModule.jitiLoadCalls).toBe(0);
|
||||
});
|
||||
|
||||
it("loads legacy root exports on demand and preserves reflection", () => {
|
||||
const lazyModule = loadRootAliasWithStubs({
|
||||
monolithicExports: {
|
||||
slowHelper: () => "loaded",
|
||||
},
|
||||
});
|
||||
const lazyRootSdk = lazyModule.moduleExports;
|
||||
|
||||
expect(lazyModule.createJitiCalls).toBe(0);
|
||||
expect("slowHelper" in lazyRootSdk).toBe(true);
|
||||
expect(lazyModule.createJitiCalls).toBe(1);
|
||||
expect(lazyModule.jitiLoadCalls).toBe(1);
|
||||
expect((lazyRootSdk.slowHelper as () => string)()).toBe("loaded");
|
||||
expect(Object.keys(lazyRootSdk)).toContain("slowHelper");
|
||||
expect(Object.getOwnPropertyDescriptor(lazyRootSdk, "slowHelper")).toBeDefined();
|
||||
});
|
||||
|
||||
it("loads legacy root exports through the merged root wrapper", { timeout: 240_000 }, () => {
|
||||
expect(typeof rootSdk.resolveControlCommandGate).toBe("function");
|
||||
expect(typeof rootSdk.default).toBe("object");
|
||||
|
||||
Reference in New Issue
Block a user