fix(plugins): scope sdk aliases to loaded module paths

This commit is contained in:
Vincent Koc
2026-03-19 15:06:35 -07:00
parent a245916dcb
commit d80b83e8e3
2 changed files with 64 additions and 10 deletions

View File

@@ -3462,6 +3462,47 @@ module.exports = {
expect(subpaths).toEqual(["channel-runtime", "core"]);
});
it("builds plugin-sdk aliases from the module being loaded, not the loader location", () => {
const fixture = createPluginSdkAliasFixture({
srcFile: "channel-runtime.ts",
distFile: "channel-runtime.js",
packageExports: {
"./plugin-sdk/channel-runtime": { default: "./dist/plugin-sdk/channel-runtime.js" },
},
});
const sourceRootAlias = path.join(fixture.root, "src", "plugin-sdk", "root-alias.cjs");
const distRootAlias = path.join(fixture.root, "dist", "plugin-sdk", "root-alias.cjs");
fs.writeFileSync(sourceRootAlias, "module.exports = {};\n", "utf-8");
fs.writeFileSync(distRootAlias, "module.exports = {};\n", "utf-8");
const sourcePluginEntry = path.join(fixture.root, "extensions", "demo", "src", "index.ts");
fs.mkdirSync(path.dirname(sourcePluginEntry), { recursive: true });
fs.writeFileSync(sourcePluginEntry, 'export const plugin = "demo";\n', "utf-8");
const sourceAliases = withEnv({ NODE_ENV: undefined }, () =>
__testing.buildPluginLoaderAliasMap(sourcePluginEntry),
);
expect(fs.realpathSync(sourceAliases["openclaw/plugin-sdk"] ?? "")).toBe(
fs.realpathSync(sourceRootAlias),
);
expect(fs.realpathSync(sourceAliases["openclaw/plugin-sdk/channel-runtime"] ?? "")).toBe(
fs.realpathSync(path.join(fixture.root, "src", "plugin-sdk", "channel-runtime.ts")),
);
const distPluginEntry = path.join(fixture.root, "dist", "extensions", "demo", "index.js");
fs.mkdirSync(path.dirname(distPluginEntry), { recursive: true });
fs.writeFileSync(distPluginEntry, 'export const plugin = "demo";\n', "utf-8");
const distAliases = withEnv({ NODE_ENV: undefined }, () =>
__testing.buildPluginLoaderAliasMap(distPluginEntry),
);
expect(fs.realpathSync(distAliases["openclaw/plugin-sdk"] ?? "")).toBe(
fs.realpathSync(distRootAlias),
);
expect(fs.realpathSync(distAliases["openclaw/plugin-sdk/channel-runtime"] ?? "")).toBe(
fs.realpathSync(path.join(fixture.root, "dist", "plugin-sdk", "channel-runtime.js")),
);
});
it("does not resolve plugin-sdk alias files from cwd fallback when package root is not an OpenClaw root", () => {
const fixture = createPluginSdkAliasFixture({
srcFile: "channel-runtime.ts",

View File

@@ -104,8 +104,20 @@ function resolveLoaderModulePath(params: LoaderModuleResolveParams = {}): string
return params.modulePath ?? fileURLToPath(params.moduleUrl ?? import.meta.url);
}
const resolvePluginSdkAlias = (): string | null =>
resolvePluginSdkAliasFile({ srcFile: "root-alias.cjs", distFile: "root-alias.cjs" });
const resolvePluginSdkAlias = (params: LoaderModuleResolveParams = {}): string | null =>
resolvePluginSdkAliasFile({
srcFile: "root-alias.cjs",
distFile: "root-alias.cjs",
...params,
});
function buildPluginLoaderAliasMap(modulePath: string): Record<string, string> {
const pluginSdkAlias = resolvePluginSdkAlias({ modulePath });
return {
...(pluginSdkAlias ? { "openclaw/plugin-sdk": pluginSdkAlias } : {}),
...resolvePluginSdkScopedAliasMap({ modulePath }),
};
}
function resolvePluginRuntimeModulePath(params: LoaderModuleResolveParams = {}): string | null {
try {
@@ -138,6 +150,7 @@ function resolvePluginRuntimeModulePath(params: LoaderModuleResolveParams = {}):
export const __testing = {
buildPluginLoaderJitiOptions,
buildPluginLoaderAliasMap,
listPluginSdkAliasCandidates,
listPluginSdkExportedSubpaths,
resolvePluginSdkScopedAliasMap,
@@ -704,18 +717,18 @@ export function loadOpenClawPlugins(options: PluginLoadOptions = {}): PluginRegi
}
// Lazy: avoid creating the Jiti loader when all plugins are disabled (common in unit tests).
const jitiLoaders = new Map<boolean, ReturnType<typeof createJiti>>();
const jitiLoaders = new Map<string, ReturnType<typeof createJiti>>();
const getJiti = (modulePath: string) => {
const tryNative = shouldPreferNativeJiti(modulePath);
const cached = jitiLoaders.get(tryNative);
const aliasMap = buildPluginLoaderAliasMap(modulePath);
const cacheKey = JSON.stringify({
tryNative,
aliasMap: Object.entries(aliasMap).toSorted(([left], [right]) => left.localeCompare(right)),
});
const cached = jitiLoaders.get(cacheKey);
if (cached) {
return cached;
}
const pluginSdkAlias = resolvePluginSdkAlias();
const aliasMap = {
...(pluginSdkAlias ? { "openclaw/plugin-sdk": pluginSdkAlias } : {}),
...resolvePluginSdkScopedAliasMap(),
};
const loader = createJiti(import.meta.url, {
...buildPluginLoaderJitiOptions(aliasMap),
// Source .ts runtime shims import sibling ".js" specifiers that only exist
@@ -724,7 +737,7 @@ export function loadOpenClawPlugins(options: PluginLoadOptions = {}): PluginRegi
// loading for the canonical built module graph.
tryNative,
});
jitiLoaders.set(tryNative, loader);
jitiLoaders.set(cacheKey, loader);
return loader;
};