mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-16 19:51:11 +00:00
153 lines
4.8 KiB
TypeScript
153 lines
4.8 KiB
TypeScript
import fs from "node:fs";
|
|
import path from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
import { loadBundledPluginPublicSurfaceModuleSync } from "../plugin-sdk/facade-loader.js";
|
|
import { resolveBundledPluginsDir } from "../plugins/bundled-dir.js";
|
|
import {
|
|
findBundledPluginMetadataById,
|
|
type BundledPluginMetadata,
|
|
} from "../plugins/bundled-plugin-metadata.js";
|
|
import { normalizeBundledPluginArtifactSubpath } from "../plugins/public-surface-runtime.js";
|
|
import { resolveLoaderPackageRoot } 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));
|
|
|
|
type BundledPluginPublicSurfaceMetadata = Pick<BundledPluginMetadata, "dirName">;
|
|
|
|
function isSafeBundledPluginDirName(pluginId: string): boolean {
|
|
return /^[a-z0-9][a-z0-9._-]*$/u.test(pluginId);
|
|
}
|
|
|
|
function readPluginManifestId(pluginDir: string): string | undefined {
|
|
try {
|
|
const manifestPath = path.join(pluginDir, "openclaw.plugin.json");
|
|
const parsed = JSON.parse(fs.readFileSync(manifestPath, "utf-8")) as { id?: unknown };
|
|
return typeof parsed.id === "string" ? parsed.id : undefined;
|
|
} catch {
|
|
return undefined;
|
|
}
|
|
}
|
|
|
|
function findBundledPluginMetadataFast(
|
|
pluginId: string,
|
|
): BundledPluginPublicSurfaceMetadata | undefined {
|
|
if (!isSafeBundledPluginDirName(pluginId)) {
|
|
return undefined;
|
|
}
|
|
const roots = [
|
|
resolveBundledPluginsDir(),
|
|
path.resolve(OPENCLAW_PACKAGE_ROOT, "extensions"),
|
|
path.resolve(OPENCLAW_PACKAGE_ROOT, "dist-runtime", "extensions"),
|
|
path.resolve(OPENCLAW_PACKAGE_ROOT, "dist", "extensions"),
|
|
].filter(
|
|
(entry, index, values): entry is string => Boolean(entry) && values.indexOf(entry) === index,
|
|
);
|
|
|
|
for (const root of roots) {
|
|
const pluginDir = path.join(root, pluginId);
|
|
if (readPluginManifestId(pluginDir) === pluginId) {
|
|
return { dirName: pluginId };
|
|
}
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
function findBundledPluginMetadata(pluginId: string): BundledPluginPublicSurfaceMetadata {
|
|
const metadata =
|
|
findBundledPluginMetadataFast(pluginId) ?? findBundledPluginMetadataById(pluginId);
|
|
if (!metadata) {
|
|
throw new Error(`Unknown bundled plugin id: ${pluginId}`);
|
|
}
|
|
return metadata;
|
|
}
|
|
|
|
export function loadBundledPluginPublicSurfaceSync<T extends object>(params: {
|
|
pluginId: string;
|
|
artifactBasename: string;
|
|
}): T {
|
|
const metadata = findBundledPluginMetadata(params.pluginId);
|
|
return loadBundledPluginPublicSurfaceModuleSync<T>({
|
|
dirName: metadata.dirName,
|
|
artifactBasename: normalizeBundledPluginArtifactSubpath(params.artifactBasename),
|
|
});
|
|
}
|
|
|
|
export function loadBundledPluginApiSync<T extends object>(pluginId: string): T {
|
|
return loadBundledPluginPublicSurfaceSync<T>({
|
|
pluginId,
|
|
artifactBasename: "api.js",
|
|
});
|
|
}
|
|
|
|
export function loadBundledPluginContractApiSync<T extends object>(pluginId: string): T {
|
|
return loadBundledPluginPublicSurfaceSync<T>({
|
|
pluginId,
|
|
artifactBasename: "contract-api.js",
|
|
});
|
|
}
|
|
|
|
export function loadBundledPluginRuntimeApiSync<T extends object>(pluginId: string): T {
|
|
return loadBundledPluginPublicSurfaceSync<T>({
|
|
pluginId,
|
|
artifactBasename: "runtime-api.js",
|
|
});
|
|
}
|
|
|
|
export function loadBundledPluginTestApiSync<T extends object>(pluginId: string): T {
|
|
return loadBundledPluginPublicSurfaceSync<T>({
|
|
pluginId,
|
|
artifactBasename: "test-api.js",
|
|
});
|
|
}
|
|
|
|
export function resolveBundledPluginPublicModulePath(params: {
|
|
pluginId: string;
|
|
artifactBasename: string;
|
|
}): string {
|
|
const metadata = findBundledPluginMetadata(params.pluginId);
|
|
return path.resolve(
|
|
OPENCLAW_PACKAGE_ROOT,
|
|
"extensions",
|
|
metadata.dirName,
|
|
normalizeBundledPluginArtifactSubpath(params.artifactBasename),
|
|
);
|
|
}
|
|
|
|
function resolveVitestSourceModulePath(targetPath: string): string {
|
|
if (!targetPath.endsWith(".js")) {
|
|
return targetPath;
|
|
}
|
|
const sourcePath = `${targetPath.slice(0, -".js".length)}.ts`;
|
|
return pathExists(sourcePath) ? sourcePath : targetPath;
|
|
}
|
|
|
|
function pathExists(filePath: string): boolean {
|
|
try {
|
|
return Boolean(filePath) && path.isAbsolute(filePath) && fs.statSync(filePath).isFile();
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
export function resolveRelativeBundledPluginPublicModuleId(params: {
|
|
fromModuleUrl: string;
|
|
pluginId: string;
|
|
artifactBasename: string;
|
|
}): string {
|
|
const fromFilePath = fileURLToPath(params.fromModuleUrl);
|
|
const targetPath = resolveVitestSourceModulePath(
|
|
resolveBundledPluginPublicModulePath({
|
|
pluginId: params.pluginId,
|
|
artifactBasename: params.artifactBasename,
|
|
}),
|
|
);
|
|
const relativePath = path
|
|
.relative(path.dirname(fromFilePath), targetPath)
|
|
.replaceAll(path.sep, "/");
|
|
return relativePath.startsWith(".") ? relativePath : `./${relativePath}`;
|
|
}
|