perf: prefer bundled plugin dist entries

This commit is contained in:
Peter Steinberger
2026-05-29 02:47:12 +01:00
parent d33c2eefce
commit d6c76eb5bf
4 changed files with 252 additions and 6 deletions

View File

@@ -390,6 +390,127 @@ describe("bundled channel entry shape guards", () => {
}
});
it("falls back through the cached loader for package-local dist entries needing SDK aliases", async () => {
const root = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-bundled-package-dist-"));
const pluginDir = path.join(root, "extensions", "alpha", "dist");
fs.mkdirSync(pluginDir, { recursive: true });
fs.writeFileSync(path.join(root, "package.json"), '{"type":"module"}\n', "utf8");
fs.writeFileSync(
path.join(pluginDir, "index.js"),
[
'import { defineBundledChannelEntry } from "openclaw/plugin-sdk/channel-entry-contract";',
"export default defineBundledChannelEntry({",
" id: 'alpha',",
" name: 'Alpha',",
" description: 'Alpha',",
" importMetaUrl: import.meta.url,",
" plugin: { specifier: './plugin.js', exportName: 'plugin' },",
"});",
"",
].join("\n"),
"utf8",
);
fs.writeFileSync(
path.join(pluginDir, "plugin.js"),
[
"export const plugin = {",
" id: 'alpha',",
" meta: { id: 'alpha', label: 'Package dist Alpha' },",
" capabilities: {},",
" config: {},",
"};",
"",
].join("\n"),
"utf8",
);
vi.doMock("./bundled-root.js", () => ({
resolveBundledChannelRootScope: () => ({
packageRoot: root,
cacheKey: `${root}:package-local-dist`,
}),
}));
vi.doMock("../../plugins/bundled-channel-runtime.js", () => ({
listBundledChannelPluginMetadata: () => [
{
...alphaChannelMetadata(),
source: {
source: path.join(root, "extensions", "alpha", "index.ts"),
built: path.join(root, "extensions", "alpha", "index.ts"),
},
},
],
resolveBundledChannelGeneratedPath: () => path.join(pluginDir, "index.js"),
}));
try {
const bundled = await importFreshModule<typeof import("./bundled.js")>(
import.meta.url,
"./bundled.js?scope=bundled-package-local-dist-sdk-alias",
);
expect(bundled.requireBundledChannelPlugin("alpha").meta.label).toBe("Package dist Alpha");
} finally {
fs.rmSync(root, { recursive: true, force: true });
}
});
it("falls back through the cached loader for direct override dist entries needing SDK aliases", async () => {
const root = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-bundled-direct-dist-"));
const previousBundledPluginsDir = process.env.OPENCLAW_BUNDLED_PLUGINS_DIR;
const pluginsRoot = path.join(root, "bundled-plugins");
const pluginDir = path.join(pluginsRoot, "alpha", "dist");
fs.mkdirSync(pluginDir, { recursive: true });
fs.writeFileSync(path.join(pluginsRoot, "package.json"), '{"type":"module"}\n', "utf8");
fs.writeFileSync(
path.join(pluginDir, "index.js"),
[
'import { defineBundledChannelEntry } from "openclaw/plugin-sdk/channel-entry-contract";',
"export default defineBundledChannelEntry({",
" id: 'alpha',",
" name: 'Alpha',",
" description: 'Alpha',",
" importMetaUrl: import.meta.url,",
" plugin: { specifier: './plugin.js', exportName: 'plugin' },",
"});",
"",
].join("\n"),
"utf8",
);
fs.writeFileSync(
path.join(pluginDir, "plugin.js"),
[
"export const plugin = {",
" id: 'alpha',",
" meta: { id: 'alpha', label: 'Direct dist Alpha' },",
" capabilities: {},",
" config: {},",
"};",
"",
].join("\n"),
"utf8",
);
vi.doMock("../../plugins/bundled-channel-runtime.js", () => ({
listBundledChannelPluginMetadata: () => [alphaChannelMetadata()],
resolveBundledChannelGeneratedPath: () => path.join(pluginDir, "index.js"),
}));
try {
process.env.OPENCLAW_BUNDLED_PLUGINS_DIR = pluginsRoot;
const bundled = await importFreshModule<typeof import("./bundled.js")>(
import.meta.url,
"./bundled.js?scope=bundled-direct-dist-sdk-alias",
);
expect(bundled.requireBundledChannelPlugin("alpha").meta.label).toBe("Direct dist Alpha");
} finally {
restoreBundledPluginsDir(previousBundledPluginsDir);
fs.rmSync(root, { recursive: true, force: true });
}
});
it("treats direct bundled plugin-tree overrides as scan roots", async () => {
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-bundled-direct-override-"));
const previousBundledPluginsDir = process.env.OPENCLAW_BUNDLED_PLUGINS_DIR;

View File

@@ -105,6 +105,20 @@ function isSourceModulePath(modulePath: string): boolean {
return /\.(?:c|m)?tsx?$/iu.test(modulePath);
}
function isPackageLocalBundledDistModulePath(params: {
rootScope: BundledChannelRootScope;
metadata: BundledChannelPluginMetadata;
modulePath: string;
}): boolean {
const distRoots = [
...(params.rootScope.pluginsDir
? [path.join(params.rootScope.pluginsDir, params.metadata.dirName, "dist")]
: []),
path.join(params.rootScope.packageRoot, "extensions", params.metadata.dirName, "dist"),
];
return distRoots.some((root) => isPathInside(root, params.modulePath));
}
function resolveChannelPluginModuleEntry(
moduleExport: unknown,
): BundledChannelEntryRuntimeContract | null {
@@ -259,7 +273,15 @@ function loadGeneratedBundledChannelModule(params: {
boundaryRootDir: boundaryRoot,
});
} catch (error) {
if (!isSourceModulePath(modulePath)) {
const canRetryWithCachedLoader =
isSourceModulePath(modulePath) ||
(isPackageLocalBundledDistModulePath({
rootScope: params.rootScope,
metadata: params.metadata,
modulePath,
}) &&
findMissingModuleCodeInChain(error) !== undefined);
if (!canRetryWithCachedLoader) {
throw error;
}
const loader = getCachedPluginModuleLoader({
@@ -267,7 +289,7 @@ function loadGeneratedBundledChannelModule(params: {
modulePath,
importerUrl: import.meta.url,
preferBuiltDist: true,
cacheScopeKey: "bundled-channel-source-entry",
cacheScopeKey: "bundled-channel-entry",
});
return loader(modulePath);
}

View File

@@ -4,6 +4,7 @@ import path from "node:path";
import { afterEach, describe, expect, it } from "vitest";
import {
listBundledChannelPluginMetadata,
resolveBundledChannelGeneratedPath,
resolveBundledChannelWorkspacePath,
} from "./bundled-channel-runtime.js";
@@ -39,4 +40,46 @@ describe("bundled channel runtime metadata", () => {
listBundledChannelPluginMetadata({ rootDir: tempRoot, scanDir: missingScanDir }),
).toStrictEqual([]);
});
it("prefers package-local dist entries over source checkout channel entries", () => {
const tempRoot = createTempRoot();
const pluginRoot = path.join(tempRoot, "extensions", "slack");
fs.mkdirSync(path.join(pluginRoot, "dist"), { recursive: true });
fs.writeFileSync(path.join(pluginRoot, "index.ts"), "export default {};\n", "utf8");
fs.writeFileSync(path.join(pluginRoot, "dist", "index.js"), "export default {};\n", "utf8");
expect(
resolveBundledChannelGeneratedPath(
tempRoot,
{
source: "./index.ts",
built: "index.js",
},
"slack",
path.join(tempRoot, "extensions"),
),
).toBe(path.join(pluginRoot, "dist", "index.js"));
});
it("prefers package-local dist entries for absolute installed registry sources", () => {
const tempRoot = createTempRoot();
const pluginRoot = path.join(tempRoot, "extensions", "slack");
const builtScanRoot = path.join(tempRoot, "dist", "extensions");
fs.mkdirSync(path.join(pluginRoot, "dist"), { recursive: true });
fs.mkdirSync(path.join(builtScanRoot, "slack"), { recursive: true });
fs.writeFileSync(path.join(pluginRoot, "index.ts"), "export default {};\n", "utf8");
fs.writeFileSync(path.join(pluginRoot, "dist", "index.js"), "export default {};\n", "utf8");
expect(
resolveBundledChannelGeneratedPath(
tempRoot,
{
source: path.join(pluginRoot, "index.ts"),
built: path.join(pluginRoot, "index.ts"),
},
"slack",
builtScanRoot,
),
).toBe(path.join(pluginRoot, "dist", "index.js"));
});
});

View File

@@ -229,15 +229,73 @@ function listBundledPluginEntryBaseDirs(params: {
pluginDirName?: string;
scanDir?: string;
}): string[] {
const scanPluginRoot = params.scanDir
? path.resolve(params.scanDir, params.pluginDirName ?? "")
: undefined;
const baseDirs = [
...(params.scanDir ? [path.resolve(params.scanDir, params.pluginDirName ?? "")] : []),
...(scanPluginRoot ? [path.resolve(scanPluginRoot, "dist")] : []),
...(scanPluginRoot ? [scanPluginRoot] : []),
path.resolve(params.rootDir, "dist", "extensions", params.pluginDirName ?? ""),
path.resolve(params.rootDir, "dist-runtime", "extensions", params.pluginDirName ?? ""),
path.resolve(params.rootDir, "extensions", params.pluginDirName ?? "", "dist"),
path.resolve(params.rootDir, "extensions", params.pluginDirName ?? ""),
];
return uniqueStrings(baseDirs);
}
function isPathInsideRoot(rootDir: string, targetPath: string): boolean {
const relative = path.relative(rootDir, targetPath);
return relative !== ".." && !relative.startsWith(`..${path.sep}`) && !path.isAbsolute(relative);
}
function listBundledPluginEntryRoots(params: {
rootDir: string;
pluginDirName?: string;
scanDir?: string;
}): string[] {
const roots = [
...(params.scanDir ? [path.resolve(params.scanDir, params.pluginDirName ?? "")] : []),
path.resolve(params.rootDir, "extensions", params.pluginDirName ?? ""),
path.resolve(params.rootDir, "dist", "extensions", params.pluginDirName ?? ""),
path.resolve(params.rootDir, "dist-runtime", "extensions", params.pluginDirName ?? ""),
];
return uniqueStrings(roots);
}
function listBundledPluginEntrySearchPaths(
entry: BundledPluginPathPair,
params: {
rootDir: string;
pluginDirName?: string;
scanDir?: string;
},
): string[] {
const paths: string[] = [];
const roots = listBundledPluginEntryRoots(params);
for (const rawEntry of [entry.built, entry.source]) {
if (typeof rawEntry !== "string" || rawEntry.length === 0) {
continue;
}
if (!path.isAbsolute(rawEntry)) {
paths.push(rawEntry);
continue;
}
const normalizedEntry = path.normalize(rawEntry);
for (const root of roots) {
if (!isPathInsideRoot(root, normalizedEntry)) {
continue;
}
const relativeEntry = path.relative(root, normalizedEntry);
const builtEntry = rewriteBundledPluginEntryToBuiltPath(relativeEntry);
if (builtEntry) {
paths.push(builtEntry);
}
paths.push(relativeEntry);
}
}
return uniqueStrings(paths);
}
export function resolveBundledPluginGeneratedPath(
rootDir: string,
entry: BundledPluginPathPair | undefined,
@@ -247,9 +305,11 @@ export function resolveBundledPluginGeneratedPath(
if (!entry) {
return null;
}
const entryOrder = [entry.built, entry.source].filter(
(candidate): candidate is string => typeof candidate === "string" && candidate.length > 0,
);
const entryOrder = listBundledPluginEntrySearchPaths(entry, {
rootDir,
pluginDirName,
...(scanDir ? { scanDir } : {}),
});
const baseDirs = listBundledPluginEntryBaseDirs({
rootDir,
pluginDirName,