diff --git a/src/plugin-sdk/facade-loader.ts b/src/plugin-sdk/facade-loader.ts index d19ac2eeed6..e6ba89332e7 100644 --- a/src/plugin-sdk/facade-loader.ts +++ b/src/plugin-sdk/facade-loader.ts @@ -10,8 +10,7 @@ import { type PluginJitiLoaderFactory, } from "../plugins/jiti-loader-cache.js"; import { - PUBLIC_SURFACE_SOURCE_EXTENSIONS, - normalizeBundledPluginArtifactSubpath, + resolveBundledPluginSourcePublicSurfacePath, resolveBundledPluginPublicSurfacePath, } from "../plugins/public-surface-runtime.js"; import { resolveLoaderPackageRoot } from "../plugins/sdk-alias.js"; @@ -58,24 +57,6 @@ function createFacadeResolutionKey(params: { dirName: string; artifactBasename: return `${params.dirName}::${params.artifactBasename}::${bundledPluginsDir ? path.resolve(bundledPluginsDir) : ""}`; } -function resolveSourceFirstPublicSurfacePath(params: { - bundledPluginsDir?: string; - dirName: string; - artifactBasename: string; -}): string | null { - const artifactBasename = normalizeBundledPluginArtifactSubpath(params.artifactBasename); - const sourceBaseName = artifactBasename.replace(/\.js$/u, ""); - const sourceRoot = - params.bundledPluginsDir ?? path.resolve(getOpenClawPackageRoot(), "extensions"); - for (const ext of PUBLIC_SURFACE_SOURCE_EXTENSIONS) { - const candidate = path.resolve(sourceRoot, params.dirName, `${sourceBaseName}${ext}`); - if (fs.existsSync(candidate)) { - return candidate; - } - } - return null; -} - function resolveFacadeModuleLocationUncached(params: { dirName: string; artifactBasename: string; @@ -84,11 +65,10 @@ function resolveFacadeModuleLocationUncached(params: { const preferSource = !CURRENT_MODULE_PATH.includes(`${path.sep}dist${path.sep}`); if (preferSource) { const modulePath = - resolveSourceFirstPublicSurfacePath({ + resolveBundledPluginSourcePublicSurfacePath({ ...params, - ...(bundledPluginsDir ? { bundledPluginsDir } : {}), + sourceRoot: bundledPluginsDir ?? path.resolve(getOpenClawPackageRoot(), "extensions"), }) ?? - resolveSourceFirstPublicSurfacePath(params) ?? resolveBundledPluginPublicSurfacePath({ rootDir: getOpenClawPackageRoot(), ...(bundledPluginsDir ? { bundledPluginsDir } : {}), diff --git a/src/plugin-sdk/facade-runtime.ts b/src/plugin-sdk/facade-runtime.ts index 80a1d9a7ddc..8f615fbb4c9 100644 --- a/src/plugin-sdk/facade-runtime.ts +++ b/src/plugin-sdk/facade-runtime.ts @@ -7,6 +7,7 @@ import type { PluginManifestRecord } from "../plugins/manifest-registry.js"; import { PUBLIC_SURFACE_SOURCE_EXTENSIONS, normalizeBundledPluginArtifactSubpath, + resolveBundledPluginSourcePublicSurfacePath, resolveBundledPluginPublicSurfacePath, } from "../plugins/public-surface-runtime.js"; import { resolveLoaderPackageRoot } from "../plugins/sdk-alias.js"; @@ -42,23 +43,6 @@ function createFacadeResolutionKey(params: { dirName: string; artifactBasename: return `${params.dirName}::${params.artifactBasename}::${bundledPluginsDir ? path.resolve(bundledPluginsDir) : ""}`; } -function resolveSourceFirstPublicSurfacePath(params: { - bundledPluginsDir?: string; - dirName: string; - artifactBasename: string; -}): string | null { - const artifactBasename = normalizeBundledPluginArtifactSubpath(params.artifactBasename); - const sourceBaseName = artifactBasename.replace(/\.js$/u, ""); - const sourceRoot = params.bundledPluginsDir ?? path.resolve(OPENCLAW_PACKAGE_ROOT, "extensions"); - for (const ext of PUBLIC_SURFACE_SOURCE_EXTENSIONS) { - const candidate = path.resolve(sourceRoot, params.dirName, `${sourceBaseName}${ext}`); - if (fs.existsSync(candidate)) { - return candidate; - } - } - return null; -} - function resolveRegistryPluginModuleLocationFromRegistry(params: { registry: readonly Pick[]; dirName: string; @@ -108,11 +92,10 @@ function resolveFacadeModuleLocationUncached(params: { const preferSource = !CURRENT_MODULE_PATH.includes(`${path.sep}dist${path.sep}`); if (preferSource) { const modulePath = - resolveSourceFirstPublicSurfacePath({ + resolveBundledPluginSourcePublicSurfacePath({ ...params, - ...(bundledPluginsDir ? { bundledPluginsDir } : {}), + sourceRoot: bundledPluginsDir ?? path.resolve(OPENCLAW_PACKAGE_ROOT, "extensions"), }) ?? - resolveSourceFirstPublicSurfacePath(params) ?? resolveBundledPluginPublicSurfacePath({ rootDir: OPENCLAW_PACKAGE_ROOT, ...(bundledPluginsDir ? { bundledPluginsDir } : {}), diff --git a/src/plugins/public-surface-runtime.test.ts b/src/plugins/public-surface-runtime.test.ts index d6364eb0e08..9fd7ab7b10e 100644 --- a/src/plugins/public-surface-runtime.test.ts +++ b/src/plugins/public-surface-runtime.test.ts @@ -1,9 +1,27 @@ -import { describe, expect, it } from "vitest"; +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; +import { afterEach, describe, expect, it } from "vitest"; import { PUBLIC_SURFACE_SOURCE_EXTENSIONS, normalizeBundledPluginArtifactSubpath, + resolveBundledPluginSourcePublicSurfacePath, } from "./public-surface-runtime.js"; +const tempDirs: string[] = []; + +afterEach(() => { + for (const tempDir of tempDirs.splice(0)) { + fs.rmSync(tempDir, { recursive: true, force: true }); + } +}); + +function createTempDir(): string { + const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-public-surface-runtime-")); + tempDirs.push(tempDir); + return tempDir; +} + describe("bundled plugin public surface runtime", () => { it("exports the canonical public surface source extension list", () => { expect(PUBLIC_SURFACE_SOURCE_EXTENSIONS).toEqual([ @@ -16,6 +34,21 @@ describe("bundled plugin public surface runtime", () => { ]); }); + it("resolves source public surfaces from the shared extension list", () => { + const sourceRoot = createTempDir(); + const modulePath = path.join(sourceRoot, "demo", "api.mts"); + fs.mkdirSync(path.dirname(modulePath), { recursive: true }); + fs.writeFileSync(modulePath, "export {};\n", "utf8"); + + expect( + resolveBundledPluginSourcePublicSurfacePath({ + sourceRoot, + dirName: "demo", + artifactBasename: "api.js", + }), + ).toBe(modulePath); + }); + it("allows plugin-local nested artifact paths", () => { expect(normalizeBundledPluginArtifactSubpath("src/outbound-adapter.js")).toBe( "src/outbound-adapter.js", diff --git a/src/plugins/public-surface-runtime.ts b/src/plugins/public-surface-runtime.ts index b76d1a30a4c..a778c0cdb95 100644 --- a/src/plugins/public-surface-runtime.ts +++ b/src/plugins/public-surface-runtime.ts @@ -38,6 +38,26 @@ export function normalizeBundledPluginArtifactSubpath(artifactBasename: string): return normalized; } +export function resolveBundledPluginSourcePublicSurfacePath(params: { + sourceRoot: string; + dirName: string; + artifactBasename: string; +}): string | null { + const artifactBasename = normalizeBundledPluginArtifactSubpath(params.artifactBasename); + const sourceBaseName = artifactBasename.replace(/\.js$/u, ""); + for (const ext of PUBLIC_SURFACE_SOURCE_EXTENSIONS) { + const sourceCandidate = path.resolve( + params.sourceRoot, + params.dirName, + `${sourceBaseName}${ext}`, + ); + if (fs.existsSync(sourceCandidate)) { + return sourceCandidate; + } + } + return null; +} + export function resolveBundledPluginPublicSurfacePath(params: { rootDir: string; dirName: string; @@ -55,14 +75,11 @@ export function resolveBundledPluginPublicSurfacePath(params: { if (fs.existsSync(explicitBuiltCandidate)) { return explicitBuiltCandidate; } - - const sourceBaseName = artifactBasename.replace(/\.js$/u, ""); - for (const ext of PUBLIC_SURFACE_SOURCE_EXTENSIONS) { - const sourceCandidate = path.join(explicitPluginDir, `${sourceBaseName}${ext}`); - if (fs.existsSync(sourceCandidate)) { - return sourceCandidate; - } - } + return resolveBundledPluginSourcePublicSurfacePath({ + sourceRoot: explicitBundledPluginsDir, + dirName: params.dirName, + artifactBasename, + }); } for (const candidate of [ @@ -74,18 +91,9 @@ export function resolveBundledPluginPublicSurfacePath(params: { } } - const sourceBaseName = artifactBasename.replace(/\.js$/u, ""); - for (const ext of PUBLIC_SURFACE_SOURCE_EXTENSIONS) { - const sourceCandidate = path.resolve( - params.rootDir, - "extensions", - params.dirName, - `${sourceBaseName}${ext}`, - ); - if (fs.existsSync(sourceCandidate)) { - return sourceCandidate; - } - } - - return null; + return resolveBundledPluginSourcePublicSurfacePath({ + sourceRoot: path.resolve(params.rootDir, "extensions"), + dirName: params.dirName, + artifactBasename, + }); }