mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-04 04:14:04 +00:00
216 lines
6.4 KiB
TypeScript
216 lines
6.4 KiB
TypeScript
import fs from "node:fs";
|
|
import path from "node:path";
|
|
import { resolveUserPath } from "../utils.js";
|
|
import { areBundledPluginsDisabled, resolveBundledPluginsDir } from "./bundled-dir.js";
|
|
|
|
export const PUBLIC_SURFACE_SOURCE_EXTENSIONS = [
|
|
".ts",
|
|
".mts",
|
|
".js",
|
|
".mjs",
|
|
".cts",
|
|
".cjs",
|
|
] as const;
|
|
|
|
export function normalizeBundledPluginArtifactSubpath(artifactBasename: string): string {
|
|
if (
|
|
path.posix.isAbsolute(artifactBasename) ||
|
|
path.win32.isAbsolute(artifactBasename) ||
|
|
artifactBasename.includes("\\")
|
|
) {
|
|
throw new Error(`Bundled plugin artifact path must stay plugin-local: ${artifactBasename}`);
|
|
}
|
|
|
|
const normalized = artifactBasename.replace(/^\.\//u, "");
|
|
if (!normalized) {
|
|
throw new Error("Bundled plugin artifact path must not be empty");
|
|
}
|
|
|
|
const segments = normalized.split("/");
|
|
if (
|
|
segments.some(
|
|
(segment) =>
|
|
segment.length === 0 || segment === "." || segment === ".." || segment.includes(":"),
|
|
)
|
|
) {
|
|
throw new Error(`Bundled plugin artifact path must stay plugin-local: ${artifactBasename}`);
|
|
}
|
|
|
|
return normalized;
|
|
}
|
|
|
|
export function normalizeBundledPluginDirName(dirName: string): string {
|
|
const normalized = dirName.trim();
|
|
if (
|
|
!normalized ||
|
|
normalized === "." ||
|
|
normalized === ".." ||
|
|
normalized.includes("/") ||
|
|
normalized.includes("\\") ||
|
|
normalized.includes(":")
|
|
) {
|
|
throw new Error(`Bundled plugin dirName must be a single directory: ${dirName}`);
|
|
}
|
|
return normalized;
|
|
}
|
|
|
|
export function resolveBundledPluginSourcePublicSurfacePath(params: {
|
|
sourceRoot: string;
|
|
dirName: string;
|
|
artifactBasename: string;
|
|
}): string | null {
|
|
const artifactBasename = normalizeBundledPluginArtifactSubpath(params.artifactBasename);
|
|
const dirName = normalizeBundledPluginDirName(params.dirName);
|
|
const sourceBaseName = artifactBasename.replace(/\.js$/u, "");
|
|
for (const ext of PUBLIC_SURFACE_SOURCE_EXTENSIONS) {
|
|
const sourceCandidate = path.resolve(params.sourceRoot, dirName, `${sourceBaseName}${ext}`);
|
|
if (fs.existsSync(sourceCandidate)) {
|
|
return sourceCandidate;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
function resolvePackageFallbackForBundledDir(params: {
|
|
rootDir: string;
|
|
bundledPluginsDir: string;
|
|
dirName: string;
|
|
artifactBasename: string;
|
|
}): string | null {
|
|
const normalizedBundledDir = path.resolve(params.bundledPluginsDir);
|
|
const normalizedRootDir = path.resolve(params.rootDir);
|
|
const packageBundledDirs = [
|
|
path.join(normalizedRootDir, "dist", "extensions"),
|
|
path.join(normalizedRootDir, "dist-runtime", "extensions"),
|
|
];
|
|
if (!packageBundledDirs.includes(normalizedBundledDir)) {
|
|
return null;
|
|
}
|
|
for (const packageBundledDir of packageBundledDirs) {
|
|
if (packageBundledDir === normalizedBundledDir) {
|
|
continue;
|
|
}
|
|
const builtCandidate = path.join(packageBundledDir, params.dirName, params.artifactBasename);
|
|
if (fs.existsSync(builtCandidate)) {
|
|
return builtCandidate;
|
|
}
|
|
}
|
|
return resolveBundledPluginSourcePublicSurfacePath({
|
|
sourceRoot: path.join(normalizedRootDir, "extensions"),
|
|
dirName: params.dirName,
|
|
artifactBasename: params.artifactBasename,
|
|
});
|
|
}
|
|
|
|
function sameExistingPath(left: string, right: string): boolean {
|
|
try {
|
|
return fs.realpathSync.native(left) === fs.realpathSync.native(right);
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
function resolveExplicitEnvBundledPluginsDir(env: NodeJS.ProcessEnv): string | undefined {
|
|
const envOverride = env.OPENCLAW_BUNDLED_PLUGINS_DIR?.trim();
|
|
if (!envOverride) {
|
|
return undefined;
|
|
}
|
|
const bundledPluginsDir = resolveBundledPluginsDir(env);
|
|
if (!bundledPluginsDir) {
|
|
return undefined;
|
|
}
|
|
const requestedDir = resolveUserPath(envOverride, env);
|
|
return sameExistingPath(requestedDir, bundledPluginsDir) ? bundledPluginsDir : undefined;
|
|
}
|
|
|
|
function resolvePublicSurfaceFromBundledDir(params: {
|
|
rootDir: string;
|
|
bundledPluginsDir: string;
|
|
dirName: string;
|
|
artifactBasename: string;
|
|
}): string | null {
|
|
const pluginDir = path.resolve(params.bundledPluginsDir, params.dirName);
|
|
const builtCandidate = path.join(pluginDir, params.artifactBasename);
|
|
if (fs.existsSync(builtCandidate)) {
|
|
return builtCandidate;
|
|
}
|
|
return (
|
|
resolveBundledPluginSourcePublicSurfacePath({
|
|
sourceRoot: params.bundledPluginsDir,
|
|
dirName: params.dirName,
|
|
artifactBasename: params.artifactBasename,
|
|
}) ??
|
|
resolvePackageFallbackForBundledDir({
|
|
rootDir: params.rootDir,
|
|
bundledPluginsDir: params.bundledPluginsDir,
|
|
dirName: params.dirName,
|
|
artifactBasename: params.artifactBasename,
|
|
})
|
|
);
|
|
}
|
|
|
|
export function resolveBundledPluginPublicSurfacePath(params: {
|
|
rootDir: string;
|
|
dirName: string;
|
|
artifactBasename: string;
|
|
env?: NodeJS.ProcessEnv;
|
|
bundledPluginsDir?: string;
|
|
bundledPluginsDirMode?: "explicit" | "auto";
|
|
}): string | null {
|
|
const artifactBasename = normalizeBundledPluginArtifactSubpath(params.artifactBasename);
|
|
const dirName = normalizeBundledPluginDirName(params.dirName);
|
|
const env = params.env ?? process.env;
|
|
|
|
const explicitBundledPluginsDir =
|
|
params.bundledPluginsDirMode === "auto"
|
|
? resolveExplicitEnvBundledPluginsDir(env)
|
|
: (params.bundledPluginsDir ?? resolveExplicitEnvBundledPluginsDir(env));
|
|
if (explicitBundledPluginsDir) {
|
|
return resolvePublicSurfaceFromBundledDir({
|
|
rootDir: params.rootDir,
|
|
bundledPluginsDir: explicitBundledPluginsDir,
|
|
dirName,
|
|
artifactBasename,
|
|
});
|
|
}
|
|
|
|
if (areBundledPluginsDisabled(env)) {
|
|
return null;
|
|
}
|
|
|
|
const sourceCandidate = resolveBundledPluginSourcePublicSurfacePath({
|
|
sourceRoot: path.resolve(params.rootDir, "extensions"),
|
|
dirName,
|
|
artifactBasename,
|
|
});
|
|
if (sourceCandidate) {
|
|
return sourceCandidate;
|
|
}
|
|
|
|
const bundledPluginsDir =
|
|
params.bundledPluginsDirMode === "auto"
|
|
? params.bundledPluginsDir
|
|
: resolveBundledPluginsDir(env);
|
|
if (bundledPluginsDir) {
|
|
const bundledCandidate = resolvePublicSurfaceFromBundledDir({
|
|
rootDir: params.rootDir,
|
|
bundledPluginsDir,
|
|
dirName,
|
|
artifactBasename,
|
|
});
|
|
if (bundledCandidate) {
|
|
return bundledCandidate;
|
|
}
|
|
}
|
|
|
|
for (const candidate of [
|
|
path.resolve(params.rootDir, "dist", "extensions", dirName, artifactBasename),
|
|
path.resolve(params.rootDir, "dist-runtime", "extensions", dirName, artifactBasename),
|
|
]) {
|
|
if (fs.existsSync(candidate)) {
|
|
return candidate;
|
|
}
|
|
}
|
|
return null;
|
|
}
|