mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-31 13:38:35 +00:00
perf: prefer bundled plugin dist entries
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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"));
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user