import fs from "node:fs"; import path from "node:path"; import { fileURLToPath } from "node:url"; import { createJiti } from "jiti"; import { openBoundaryFileSync } from "../infra/boundary-file-read.js"; import { resolveBundledPluginsDir } from "../plugins/bundled-dir.js"; import { resolveBundledPluginPublicSurfacePath } from "../plugins/bundled-plugin-metadata.js"; import { buildPluginLoaderAliasMap, buildPluginLoaderJitiOptions, resolveLoaderPackageRoot, shouldPreferNativeJiti, } from "../plugins/sdk-alias.js"; const OPENCLAW_PACKAGE_ROOT = resolveLoaderPackageRoot({ modulePath: fileURLToPath(import.meta.url), moduleUrl: import.meta.url, }) ?? fileURLToPath(new URL("../..", import.meta.url)); const CURRENT_MODULE_PATH = fileURLToPath(import.meta.url); const PUBLIC_SURFACE_SOURCE_EXTENSIONS = [".ts", ".mts", ".js", ".mjs", ".cts", ".cjs"] as const; const jitiLoaders = new Map>(); const loadedFacadeModules = new Map(); function resolveSourceFirstPublicSurfacePath(params: { bundledPluginsDir?: string; dirName: string; artifactBasename: string; }): string | null { const sourceBaseName = params.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 resolveFacadeModuleLocation(params: { dirName: string; artifactBasename: string; }): { modulePath: string; boundaryRoot: string } | null { const bundledPluginsDir = resolveBundledPluginsDir(); const preferSource = !CURRENT_MODULE_PATH.includes(`${path.sep}dist${path.sep}`); if (preferSource) { const modulePath = resolveSourceFirstPublicSurfacePath({ ...params, ...(bundledPluginsDir ? { bundledPluginsDir } : {}), }) ?? resolveSourceFirstPublicSurfacePath(params) ?? resolveBundledPluginPublicSurfacePath({ rootDir: OPENCLAW_PACKAGE_ROOT, ...(bundledPluginsDir ? { bundledPluginsDir } : {}), dirName: params.dirName, artifactBasename: params.artifactBasename, }); if (!modulePath) { return null; } return { modulePath, boundaryRoot: bundledPluginsDir && modulePath.startsWith(path.resolve(bundledPluginsDir) + path.sep) ? path.resolve(bundledPluginsDir) : OPENCLAW_PACKAGE_ROOT, }; } const modulePath = resolveBundledPluginPublicSurfacePath({ rootDir: OPENCLAW_PACKAGE_ROOT, ...(bundledPluginsDir ? { bundledPluginsDir } : {}), dirName: params.dirName, artifactBasename: params.artifactBasename, }); if (!modulePath) { return null; } return { modulePath, boundaryRoot: bundledPluginsDir && modulePath.startsWith(path.resolve(bundledPluginsDir) + path.sep) ? path.resolve(bundledPluginsDir) : OPENCLAW_PACKAGE_ROOT, }; } function getJiti(modulePath: string) { const tryNative = shouldPreferNativeJiti(modulePath) || modulePath.includes(`${path.sep}dist${path.sep}`); const aliasMap = buildPluginLoaderAliasMap(modulePath, process.argv[1], import.meta.url); 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 loader = createJiti(import.meta.url, { ...buildPluginLoaderJitiOptions(aliasMap), tryNative, }); jitiLoaders.set(cacheKey, loader); return loader; } function createLazyFacadeValueLoader(load: () => T): () => T { let loaded = false; let value: T; return () => { if (!loaded) { value = load(); loaded = true; } return value; }; } function createLazyFacadeProxyValue(params: { load: () => T; target: object; }): T { const resolve = createLazyFacadeValueLoader(params.load); return new Proxy(params.target, { defineProperty(_target, property, descriptor) { return Reflect.defineProperty(resolve(), property, descriptor); }, deleteProperty(_target, property) { return Reflect.deleteProperty(resolve(), property); }, get(_target, property, receiver) { return Reflect.get(resolve(), property, receiver); }, getOwnPropertyDescriptor(_target, property) { return Reflect.getOwnPropertyDescriptor(resolve(), property); }, getPrototypeOf() { return Reflect.getPrototypeOf(resolve()); }, has(_target, property) { return Reflect.has(resolve(), property); }, isExtensible() { return Reflect.isExtensible(resolve()); }, ownKeys() { return Reflect.ownKeys(resolve()); }, preventExtensions() { return Reflect.preventExtensions(resolve()); }, set(_target, property, value, receiver) { return Reflect.set(resolve(), property, value, receiver); }, setPrototypeOf(_target, prototype) { return Reflect.setPrototypeOf(resolve(), prototype); }, }) as T; } export function createLazyFacadeObjectValue(load: () => T): T { return createLazyFacadeProxyValue({ load, target: {} }); } export function createLazyFacadeArrayValue(load: () => T): T { return createLazyFacadeProxyValue({ load, target: [] }); } export function loadBundledPluginPublicSurfaceModuleSync(params: { dirName: string; artifactBasename: string; }): T { const location = resolveFacadeModuleLocation(params); if (!location) { throw new Error( `Unable to resolve bundled plugin public surface ${params.dirName}/${params.artifactBasename}`, ); } const cached = loadedFacadeModules.get(location.modulePath); if (cached) { return cached as T; } const opened = openBoundaryFileSync({ absolutePath: location.modulePath, rootPath: location.boundaryRoot, boundaryLabel: location.boundaryRoot === OPENCLAW_PACKAGE_ROOT ? "OpenClaw package root" : "bundled plugin directory", rejectHardlinks: false, }); if (!opened.ok) { throw new Error( `Unable to open bundled plugin public surface ${params.dirName}/${params.artifactBasename}`, { cause: opened.error }, ); } fs.closeSync(opened.fd); const loaded = getJiti(location.modulePath)(location.modulePath) as T; loadedFacadeModules.set(location.modulePath, loaded); return loaded; }