diff --git a/src/plugin-sdk/facade-activation-check.runtime.ts b/src/plugin-sdk/facade-activation-check.runtime.ts index c022d6aa132..d7269390b44 100644 --- a/src/plugin-sdk/facade-activation-check.runtime.ts +++ b/src/plugin-sdk/facade-activation-check.runtime.ts @@ -16,10 +16,7 @@ import { loadPluginManifestRegistry, type PluginManifestRecord, } from "../plugins/manifest-registry.js"; -import { - PUBLIC_SURFACE_SOURCE_EXTENSIONS, - normalizeBundledPluginArtifactSubpath, -} from "../plugins/public-surface-runtime.js"; +import { resolveRegistryPluginModuleLocationFromRecords } from "./facade-resolution-shared.js"; const ALWAYS_ALLOWED_RUNTIME_DIR_NAMES = new Set([ "image-generation-core", @@ -171,30 +168,11 @@ export function resolveRegistryPluginModuleLocation(params: { cacheKey: params.resolutionKey, ...(params.env ? { env: params.env } : {}), }); - type RegistryRecord = (typeof registry)[number]; - const tiers: Array<(plugin: RegistryRecord) => boolean> = [ - (plugin) => plugin.id === params.dirName, - (plugin) => path.basename(plugin.rootDir) === params.dirName, - (plugin) => plugin.channels.includes(params.dirName), - ]; - const artifactBasename = normalizeBundledPluginArtifactSubpath(params.artifactBasename); - const sourceBaseName = artifactBasename.replace(/\.js$/u, ""); - for (const matchFn of tiers) { - for (const record of registry.filter(matchFn)) { - const rootDir = path.resolve(record.rootDir); - const builtCandidate = path.join(rootDir, artifactBasename); - if (fs.existsSync(builtCandidate)) { - return { modulePath: builtCandidate, boundaryRoot: rootDir }; - } - for (const ext of PUBLIC_SURFACE_SOURCE_EXTENSIONS) { - const sourceCandidate = path.join(rootDir, `${sourceBaseName}${ext}`); - if (fs.existsSync(sourceCandidate)) { - return { modulePath: sourceCandidate, boundaryRoot: rootDir }; - } - } - } - } - return null; + return resolveRegistryPluginModuleLocationFromRecords({ + registry, + dirName: params.dirName, + artifactBasename: params.artifactBasename, + }); } function readBundledPluginManifestRecordFromDir(params: { diff --git a/src/plugin-sdk/facade-loader.ts b/src/plugin-sdk/facade-loader.ts index b95252b6d84..f658629a6c3 100644 --- a/src/plugin-sdk/facade-loader.ts +++ b/src/plugin-sdk/facade-loader.ts @@ -9,11 +9,12 @@ import { type PluginJitiLoaderCache, type PluginJitiLoaderFactory, } from "../plugins/jiti-loader-cache.js"; -import { - resolveBundledPluginSourcePublicSurfacePath, - resolveBundledPluginPublicSurfacePath, -} from "../plugins/public-surface-runtime.js"; import { resolveLoaderPackageRoot } from "../plugins/sdk-alias.js"; +import { + createFacadeResolutionKey as createFacadeResolutionKeyShared, + resolveBundledFacadeModuleLocation, + resolveCachedFacadeModuleLocation, +} from "./facade-resolution-shared.js"; const CURRENT_MODULE_PATH = fileURLToPath(import.meta.url); @@ -58,7 +59,7 @@ function createFacadeResolutionKey(params: { env?: NodeJS.ProcessEnv; }): string { const bundledPluginsDir = resolveBundledPluginsDir(params.env ?? process.env); - return `${params.dirName}::${params.artifactBasename}::${bundledPluginsDir ? path.resolve(bundledPluginsDir) : ""}`; + return createFacadeResolutionKeyShared({ ...params, bundledPluginsDir }); } function resolveFacadeModuleLocationUncached(params: { @@ -67,48 +68,12 @@ function resolveFacadeModuleLocationUncached(params: { env?: NodeJS.ProcessEnv; }): { modulePath: string; boundaryRoot: string } | null { const bundledPluginsDir = resolveBundledPluginsDir(params.env ?? process.env); - const preferSource = !CURRENT_MODULE_PATH.includes(`${path.sep}dist${path.sep}`); - if (preferSource) { - const modulePath = - resolveBundledPluginSourcePublicSurfacePath({ - ...params, - sourceRoot: bundledPluginsDir ?? path.resolve(getOpenClawPackageRoot(), "extensions"), - }) ?? - resolveBundledPluginPublicSurfacePath({ - rootDir: getOpenClawPackageRoot(), - env: params.env, - ...(bundledPluginsDir ? { bundledPluginsDir } : {}), - dirName: params.dirName, - artifactBasename: params.artifactBasename, - }); - if (modulePath) { - return { - modulePath, - boundaryRoot: - bundledPluginsDir && modulePath.startsWith(path.resolve(bundledPluginsDir) + path.sep) - ? path.resolve(bundledPluginsDir) - : getOpenClawPackageRoot(), - }; - } - return null; - } - const modulePath = resolveBundledPluginPublicSurfacePath({ - rootDir: getOpenClawPackageRoot(), - env: params.env, - ...(bundledPluginsDir ? { bundledPluginsDir } : {}), - dirName: params.dirName, - artifactBasename: params.artifactBasename, + return resolveBundledFacadeModuleLocation({ + ...params, + currentModulePath: CURRENT_MODULE_PATH, + packageRoot: getOpenClawPackageRoot(), + bundledPluginsDir, }); - if (!modulePath) { - return null; - } - return { - modulePath, - boundaryRoot: - bundledPluginsDir && modulePath.startsWith(path.resolve(bundledPluginsDir) + path.sep) - ? path.resolve(bundledPluginsDir) - : getOpenClawPackageRoot(), - }; } function resolveFacadeModuleLocation(params: { @@ -116,13 +81,11 @@ function resolveFacadeModuleLocation(params: { artifactBasename: string; env?: NodeJS.ProcessEnv; }): { modulePath: string; boundaryRoot: string } | null { - const key = createFacadeResolutionKey(params); - if (cachedFacadeModuleLocationsByKey.has(key)) { - return cachedFacadeModuleLocationsByKey.get(key) ?? null; - } - const resolved = resolveFacadeModuleLocationUncached(params); - cachedFacadeModuleLocationsByKey.set(key, resolved); - return resolved; + return resolveCachedFacadeModuleLocation({ + cache: cachedFacadeModuleLocationsByKey, + key: createFacadeResolutionKey(params), + resolve: () => resolveFacadeModuleLocationUncached(params), + }); } function getJiti(modulePath: string) { diff --git a/src/plugin-sdk/facade-resolution-shared.ts b/src/plugin-sdk/facade-resolution-shared.ts new file mode 100644 index 00000000000..e5efef1f195 --- /dev/null +++ b/src/plugin-sdk/facade-resolution-shared.ts @@ -0,0 +1,121 @@ +import fs from "node:fs"; +import path from "node:path"; +import { + PUBLIC_SURFACE_SOURCE_EXTENSIONS, + normalizeBundledPluginArtifactSubpath, + resolveBundledPluginPublicSurfacePath, + resolveBundledPluginSourcePublicSurfacePath, +} from "../plugins/public-surface-runtime.js"; + +export type FacadeModuleLocationLike = { + modulePath: string; + boundaryRoot: string; +}; + +type FacadeRegistryRecordLike = { + id: string; + rootDir: string; + channels: readonly string[]; +}; + +export function createFacadeResolutionKey(params: { + dirName: string; + artifactBasename: string; + bundledPluginsDir?: string | null; +}): string { + return `${params.dirName}::${params.artifactBasename}::${ + params.bundledPluginsDir ? path.resolve(params.bundledPluginsDir) : "" + }`; +} + +export function resolveCachedFacadeModuleLocation(params: { + cache: Map; + key: string; + resolve: () => TLocation | null; +}): TLocation | null { + if (params.cache.has(params.key)) { + return params.cache.get(params.key) ?? null; + } + const resolved = params.resolve(); + params.cache.set(params.key, resolved); + return resolved; +} + +export function resolveFacadeBoundaryRoot(params: { + modulePath: string; + bundledPluginsDir?: string | null; + packageRoot: string; +}): string { + if (!params.bundledPluginsDir) { + return params.packageRoot; + } + const resolvedBundledPluginsDir = path.resolve(params.bundledPluginsDir); + return params.modulePath.startsWith(`${resolvedBundledPluginsDir}${path.sep}`) + ? resolvedBundledPluginsDir + : params.packageRoot; +} + +export function resolveBundledFacadeModuleLocation(params: { + currentModulePath: string; + packageRoot: string; + dirName: string; + artifactBasename: string; + env?: NodeJS.ProcessEnv; + bundledPluginsDir?: string | null; +}): FacadeModuleLocationLike | null { + const preferSource = !params.currentModulePath.includes(`${path.sep}dist${path.sep}`); + const publicSurfaceParams = { + rootDir: params.packageRoot, + env: params.env, + ...(params.bundledPluginsDir ? { bundledPluginsDir: params.bundledPluginsDir } : {}), + dirName: params.dirName, + artifactBasename: params.artifactBasename, + }; + const modulePath = preferSource + ? (resolveBundledPluginSourcePublicSurfacePath({ + dirName: params.dirName, + artifactBasename: params.artifactBasename, + sourceRoot: params.bundledPluginsDir ?? path.resolve(params.packageRoot, "extensions"), + }) ?? resolveBundledPluginPublicSurfacePath(publicSurfaceParams)) + : resolveBundledPluginPublicSurfacePath(publicSurfaceParams); + return modulePath + ? { + modulePath, + boundaryRoot: resolveFacadeBoundaryRoot({ + modulePath, + bundledPluginsDir: params.bundledPluginsDir, + packageRoot: params.packageRoot, + }), + } + : null; +} + +export function resolveRegistryPluginModuleLocationFromRecords(params: { + registry: readonly FacadeRegistryRecordLike[]; + dirName: string; + artifactBasename: string; +}): FacadeModuleLocationLike | null { + const tiers: Array<(plugin: FacadeRegistryRecordLike) => boolean> = [ + (plugin) => plugin.id === params.dirName, + (plugin) => path.basename(plugin.rootDir) === params.dirName, + (plugin) => plugin.channels.includes(params.dirName), + ]; + const artifactBasename = normalizeBundledPluginArtifactSubpath(params.artifactBasename); + const sourceBaseName = artifactBasename.replace(/\.js$/u, ""); + for (const matchFn of tiers) { + for (const record of params.registry.filter(matchFn)) { + const rootDir = path.resolve(record.rootDir); + const builtCandidate = path.join(rootDir, artifactBasename); + if (fs.existsSync(builtCandidate)) { + return { modulePath: builtCandidate, boundaryRoot: rootDir }; + } + for (const ext of PUBLIC_SURFACE_SOURCE_EXTENSIONS) { + const sourceCandidate = path.join(rootDir, `${sourceBaseName}${ext}`); + if (fs.existsSync(sourceCandidate)) { + return { modulePath: sourceCandidate, boundaryRoot: rootDir }; + } + } + } + } + return null; +} diff --git a/src/plugin-sdk/facade-runtime.ts b/src/plugin-sdk/facade-runtime.ts index 88f034fee4b..3b4bc1eebe1 100644 --- a/src/plugin-sdk/facade-runtime.ts +++ b/src/plugin-sdk/facade-runtime.ts @@ -1,4 +1,3 @@ -import fs from "node:fs"; import { createRequire } from "node:module"; import path from "node:path"; import { fileURLToPath } from "node:url"; @@ -7,13 +6,6 @@ import { getCachedPluginJitiLoader, type PluginJitiLoaderCache, } from "../plugins/jiti-loader-cache.js"; -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"; import { loadBundledPluginPublicSurfaceModuleSync as loadBundledPluginPublicSurfaceModuleSyncLight, @@ -21,6 +13,12 @@ import { resetFacadeLoaderStateForTest, type FacadeModuleLocation, } from "./facade-loader.js"; +import { + createFacadeResolutionKey as createFacadeResolutionKeyShared, + resolveBundledFacadeModuleLocation, + resolveCachedFacadeModuleLocation, + resolveRegistryPluginModuleLocationFromRecords, +} from "./facade-resolution-shared.js"; export { createLazyFacadeArrayValue, createLazyFacadeObjectValue, @@ -61,38 +59,7 @@ function createFacadeResolutionKey(params: { env?: NodeJS.ProcessEnv; }): string { const bundledPluginsDir = resolveBundledPluginsDir(params.env ?? process.env); - return `${params.dirName}::${params.artifactBasename}::${bundledPluginsDir ? path.resolve(bundledPluginsDir) : ""}`; -} - -function resolveRegistryPluginModuleLocationFromRegistry(params: { - registry: readonly Pick[]; - dirName: string; - artifactBasename: string; -}): { modulePath: string; boundaryRoot: string } | null { - type RegistryRecord = (typeof params.registry)[number]; - const tiers: Array<(plugin: RegistryRecord) => boolean> = [ - (plugin) => plugin.id === params.dirName, - (plugin) => path.basename(plugin.rootDir) === params.dirName, - (plugin) => plugin.channels.includes(params.dirName), - ]; - const artifactBasename = normalizeBundledPluginArtifactSubpath(params.artifactBasename); - const sourceBaseName = artifactBasename.replace(/\.js$/u, ""); - for (const matchFn of tiers) { - for (const record of params.registry.filter(matchFn)) { - const rootDir = path.resolve(record.rootDir); - const builtCandidate = path.join(rootDir, artifactBasename); - if (fs.existsSync(builtCandidate)) { - return { modulePath: builtCandidate, boundaryRoot: rootDir }; - } - for (const ext of PUBLIC_SURFACE_SOURCE_EXTENSIONS) { - const sourceCandidate = path.join(rootDir, `${sourceBaseName}${ext}`); - if (fs.existsSync(sourceCandidate)) { - return { modulePath: sourceCandidate, boundaryRoot: rootDir }; - } - } - } - } - return null; + return createFacadeResolutionKeyShared({ ...params, bundledPluginsDir }); } function resolveRegistryPluginModuleLocation(params: { @@ -106,56 +73,20 @@ function resolveRegistryPluginModuleLocation(params: { }); } -function resolveFacadeBoundaryRoot(modulePath: string, bundledPluginsDir: string | undefined) { - if (!bundledPluginsDir) { - return OPENCLAW_PACKAGE_ROOT; - } - const resolvedBundledPluginsDir = path.resolve(bundledPluginsDir); - return modulePath.startsWith(`${resolvedBundledPluginsDir}${path.sep}`) - ? resolvedBundledPluginsDir - : OPENCLAW_PACKAGE_ROOT; -} - function resolveFacadeModuleLocationUncached(params: { dirName: string; artifactBasename: string; env?: NodeJS.ProcessEnv; }): { modulePath: string; boundaryRoot: string } | null { const bundledPluginsDir = resolveBundledPluginsDir(params.env ?? process.env); - const preferSource = !CURRENT_MODULE_PATH.includes(`${path.sep}dist${path.sep}`); - if (preferSource) { - const modulePath = - resolveBundledPluginSourcePublicSurfacePath({ - ...params, - sourceRoot: bundledPluginsDir ?? path.resolve(OPENCLAW_PACKAGE_ROOT, "extensions"), - }) ?? - resolveBundledPluginPublicSurfacePath({ - rootDir: OPENCLAW_PACKAGE_ROOT, - env: params.env, - ...(bundledPluginsDir ? { bundledPluginsDir } : {}), - dirName: params.dirName, - artifactBasename: params.artifactBasename, - }); - if (modulePath) { - return { - modulePath, - boundaryRoot: resolveFacadeBoundaryRoot(modulePath, bundledPluginsDir), - }; - } - return resolveRegistryPluginModuleLocation(params); - } - const modulePath = resolveBundledPluginPublicSurfacePath({ - rootDir: OPENCLAW_PACKAGE_ROOT, - env: params.env, - ...(bundledPluginsDir ? { bundledPluginsDir } : {}), - dirName: params.dirName, - artifactBasename: params.artifactBasename, + const bundledLocation = resolveBundledFacadeModuleLocation({ + ...params, + currentModulePath: CURRENT_MODULE_PATH, + packageRoot: OPENCLAW_PACKAGE_ROOT, + bundledPluginsDir, }); - if (modulePath) { - return { - modulePath, - boundaryRoot: resolveFacadeBoundaryRoot(modulePath, bundledPluginsDir), - }; + if (bundledLocation) { + return bundledLocation; } return resolveRegistryPluginModuleLocation(params); } @@ -165,13 +96,11 @@ function resolveFacadeModuleLocation(params: { artifactBasename: string; env?: NodeJS.ProcessEnv; }): { modulePath: string; boundaryRoot: string } | null { - const key = createFacadeResolutionKey(params); - if (cachedFacadeModuleLocationsByKey.has(key)) { - return cachedFacadeModuleLocationsByKey.get(key) ?? null; - } - const resolved = resolveFacadeModuleLocationUncached(params); - cachedFacadeModuleLocationsByKey.set(key, resolved); - return resolved; + return resolveCachedFacadeModuleLocation({ + cache: cachedFacadeModuleLocationsByKey, + key: createFacadeResolutionKey(params), + resolve: () => resolveFacadeModuleLocationUncached(params), + }); } type BundledPluginPublicSurfaceParams = { @@ -324,7 +253,7 @@ export function resetFacadeRuntimeStateForTest(): void { export const __testing = { loadFacadeModuleAtLocationSync, - resolveRegistryPluginModuleLocationFromRegistry, + resolveRegistryPluginModuleLocationFromRegistry: resolveRegistryPluginModuleLocationFromRecords, resolveFacadeModuleLocation, evaluateBundledPluginPublicSurfaceAccess: (( ...args: Parameters<