fix(plugins): resolve lazy runtime from package root

This commit is contained in:
Peter Steinberger 2026-03-16 05:12:19 +00:00
parent d937b61fb3
commit 69c12c2b11
2 changed files with 134 additions and 13 deletions

View File

@ -287,6 +287,11 @@ function createPluginSdkAliasFixture(params?: {
const distFile = path.join(root, "dist", "plugin-sdk", params?.distFile ?? "index.js");
mkdirSafe(path.dirname(srcFile));
mkdirSafe(path.dirname(distFile));
fs.writeFileSync(
path.join(root, "package.json"),
JSON.stringify({ name: "openclaw", type: "module" }, null, 2),
"utf-8",
);
fs.writeFileSync(srcFile, params?.srcBody ?? "export {};\n", "utf-8");
fs.writeFileSync(distFile, params?.distBody ?? "export {};\n", "utf-8");
return { root, srcFile, distFile };
@ -308,6 +313,30 @@ function createExtensionApiAliasFixture(params?: { srcBody?: string; distBody?:
return { root, srcFile, distFile };
}
function createPluginRuntimeAliasFixture(params?: { srcBody?: string; distBody?: string }) {
const root = makeTempDir();
const srcFile = path.join(root, "src", "plugins", "runtime", "index.ts");
const distFile = path.join(root, "dist", "plugins", "runtime", "index.js");
mkdirSafe(path.dirname(srcFile));
mkdirSafe(path.dirname(distFile));
fs.writeFileSync(
path.join(root, "package.json"),
JSON.stringify({ name: "openclaw", type: "module" }, null, 2),
"utf-8",
);
fs.writeFileSync(
srcFile,
params?.srcBody ?? "export const createPluginRuntime = () => ({});\n",
"utf-8",
);
fs.writeFileSync(
distFile,
params?.distBody ?? "export const createPluginRuntime = () => ({});\n",
"utf-8",
);
return { root, srcFile, distFile };
}
afterEach(() => {
clearPluginLoaderCache();
if (prevBundledDir === undefined) {
@ -2957,4 +2986,42 @@ module.exports = {
);
expect(resolved).toBe(srcFile);
});
it("resolves plugin-sdk alias from package root when loader runs from transpiler cache path", () => {
const { root, srcFile } = createPluginSdkAliasFixture();
const resolved = withEnv({ NODE_ENV: undefined }, () =>
__testing.resolvePluginSdkAliasFile({
srcFile: "index.ts",
distFile: "index.js",
modulePath: "/tmp/tsx-cache/openclaw-loader.js",
argv1: path.join(root, "openclaw.mjs"),
}),
);
expect(resolved).toBe(srcFile);
});
it("resolves extension-api alias from package root when loader runs from transpiler cache path", () => {
const { root, srcFile } = createExtensionApiAliasFixture();
const resolved = withEnv({ NODE_ENV: undefined }, () =>
__testing.resolveExtensionApiAlias({
modulePath: "/tmp/tsx-cache/openclaw-loader.js",
argv1: path.join(root, "openclaw.mjs"),
}),
);
expect(resolved).toBe(srcFile);
});
it("resolves plugin runtime module from package root when loader runs from transpiler cache path", () => {
const { root, srcFile } = createPluginRuntimeAliasFixture();
const resolved = withEnv({ NODE_ENV: undefined }, () =>
__testing.resolvePluginRuntimeModulePath({
modulePath: "/tmp/tsx-cache/openclaw-loader.js",
argv1: path.join(root, "openclaw.mjs"),
}),
);
expect(resolved).toBe(srcFile);
});
});

View File

@ -71,6 +71,34 @@ const defaultLogger = () => createSubsystemLogger("plugins");
type PluginSdkAliasCandidateKind = "dist" | "src";
type LoaderModuleResolveParams = {
modulePath?: string;
argv1?: string;
cwd?: string;
moduleUrl?: string;
};
function resolveLoaderModulePath(params: LoaderModuleResolveParams = {}): string {
return params.modulePath ?? fileURLToPath(params.moduleUrl ?? import.meta.url);
}
function resolveLoaderPackageRoot(
params: LoaderModuleResolveParams & { modulePath: string },
): string | null {
const cwd = params.cwd ?? path.dirname(params.modulePath);
const fromModulePath = resolveOpenClawPackageRootSync({ cwd });
if (fromModulePath) {
return fromModulePath;
}
const argv1 = params.argv1 ?? process.argv[1];
const moduleUrl = params.moduleUrl ?? (params.modulePath ? undefined : import.meta.url);
return resolveOpenClawPackageRootSync({
cwd,
...(argv1 ? { argv1 } : {}),
...(moduleUrl ? { moduleUrl } : {}),
});
}
function resolvePluginSdkAliasCandidateOrder(params: {
modulePath: string;
isProduction: boolean;
@ -84,11 +112,22 @@ function listPluginSdkAliasCandidates(params: {
srcFile: string;
distFile: string;
modulePath: string;
argv1?: string;
cwd?: string;
moduleUrl?: string;
}) {
const orderedKinds = resolvePluginSdkAliasCandidateOrder({
modulePath: params.modulePath,
isProduction: process.env.NODE_ENV === "production",
});
const packageRoot = resolveLoaderPackageRoot(params);
if (packageRoot) {
const candidateMap = {
src: path.join(packageRoot, "src", "plugin-sdk", params.srcFile),
dist: path.join(packageRoot, "dist", "plugin-sdk", params.distFile),
} as const;
return orderedKinds.map((kind) => candidateMap[kind]);
}
let cursor = path.dirname(params.modulePath);
const candidates: string[] = [];
for (let i = 0; i < 6; i += 1) {
@ -112,13 +151,19 @@ const resolvePluginSdkAliasFile = (params: {
srcFile: string;
distFile: string;
modulePath?: string;
argv1?: string;
cwd?: string;
moduleUrl?: string;
}): string | null => {
try {
const modulePath = params.modulePath ?? fileURLToPath(import.meta.url);
const modulePath = resolveLoaderModulePath(params);
for (const candidate of listPluginSdkAliasCandidates({
srcFile: params.srcFile,
distFile: params.distFile,
modulePath,
argv1: params.argv1,
cwd: params.cwd,
moduleUrl: params.moduleUrl,
})) {
if (fs.existsSync(candidate)) {
return candidate;
@ -133,12 +178,10 @@ const resolvePluginSdkAliasFile = (params: {
const resolvePluginSdkAlias = (): string | null =>
resolvePluginSdkAliasFile({ srcFile: "root-alias.cjs", distFile: "root-alias.cjs" });
const resolveExtensionApiAlias = (params: { modulePath?: string } = {}): string | null => {
const resolveExtensionApiAlias = (params: LoaderModuleResolveParams = {}): string | null => {
try {
const modulePath = params.modulePath ?? fileURLToPath(import.meta.url);
const packageRoot = resolveOpenClawPackageRootSync({
cwd: path.dirname(modulePath),
});
const modulePath = resolveLoaderModulePath(params);
const packageRoot = resolveLoaderPackageRoot({ ...params, modulePath });
if (!packageRoot) {
return null;
}
@ -163,14 +206,24 @@ const resolveExtensionApiAlias = (params: { modulePath?: string } = {}): string
return null;
};
function resolvePluginRuntimeModulePath(params: { modulePath?: string } = {}): string | null {
function resolvePluginRuntimeModulePath(params: LoaderModuleResolveParams = {}): string | null {
try {
const modulePath = params.modulePath ?? fileURLToPath(import.meta.url);
const moduleDir = path.dirname(modulePath);
const candidates = [
path.join(moduleDir, "runtime", "index.ts"),
path.join(moduleDir, "runtime", "index.js"),
];
const modulePath = resolveLoaderModulePath(params);
const orderedKinds = resolvePluginSdkAliasCandidateOrder({
modulePath,
isProduction: process.env.NODE_ENV === "production",
});
const packageRoot = resolveLoaderPackageRoot({ ...params, modulePath });
const candidates = packageRoot
? orderedKinds.map((kind) =>
kind === "src"
? path.join(packageRoot, "src", "plugins", "runtime", "index.ts")
: path.join(packageRoot, "dist", "plugins", "runtime", "index.js"),
)
: [
path.join(path.dirname(modulePath), "runtime", "index.ts"),
path.join(path.dirname(modulePath), "runtime", "index.js"),
];
for (const candidate of candidates) {
if (fs.existsSync(candidate)) {
return candidate;
@ -233,6 +286,7 @@ export const __testing = {
resolveExtensionApiAlias,
resolvePluginSdkAliasCandidateOrder,
resolvePluginSdkAliasFile,
resolvePluginRuntimeModulePath,
maxPluginRegistryCacheEntries: MAX_PLUGIN_REGISTRY_CACHE_ENTRIES,
};