mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 14:00:47 +00:00
refactor: share plugin package entry resolution
This commit is contained in:
@@ -1,7 +1,6 @@
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { matchBoundaryFileOpenFailure, openBoundaryFileSync } from "../infra/boundary-file-read.js";
|
||||
import { resolveBoundaryPathSync } from "../infra/boundary-path.js";
|
||||
import { openBoundaryFileSync } from "../infra/boundary-file-read.js";
|
||||
import {
|
||||
normalizeLowercaseStringOrEmpty,
|
||||
normalizeOptionalString,
|
||||
@@ -19,7 +18,10 @@ import {
|
||||
type OpenClawPackageManifest,
|
||||
type PackageManifest,
|
||||
} from "./manifest.js";
|
||||
import { listBuiltRuntimeEntryCandidates } from "./package-entrypoints.js";
|
||||
import {
|
||||
resolvePackageRuntimeExtensionSources,
|
||||
resolvePackageSetupSource,
|
||||
} from "./package-entry-resolution.js";
|
||||
import { formatPosixMode, isPathInside, safeRealpathSync, safeStatSync } from "./path-safety.js";
|
||||
import type { PluginOrigin } from "./plugin-origin.types.js";
|
||||
import { resolvePluginCacheInputs, resolvePluginSourceRoots } from "./roots.js";
|
||||
@@ -555,245 +557,6 @@ function discoverBundleInRoot(params: {
|
||||
return "added";
|
||||
}
|
||||
|
||||
function resolvePackageEntrySource(params: {
|
||||
packageDir: string;
|
||||
entryPath: string;
|
||||
sourceLabel: string;
|
||||
diagnostics: PluginDiagnostic[];
|
||||
rejectHardlinks?: boolean;
|
||||
}): string | null {
|
||||
const source = path.resolve(params.packageDir, params.entryPath);
|
||||
const rejectHardlinks = params.rejectHardlinks ?? true;
|
||||
const candidates = [source];
|
||||
const openCandidate = (absolutePath: string): string | null => {
|
||||
const opened = openBoundaryFileSync({
|
||||
absolutePath,
|
||||
rootPath: params.packageDir,
|
||||
boundaryLabel: "plugin package directory",
|
||||
rejectHardlinks,
|
||||
});
|
||||
if (!opened.ok) {
|
||||
return matchBoundaryFileOpenFailure(opened, {
|
||||
path: () => null,
|
||||
io: () => {
|
||||
params.diagnostics.push({
|
||||
level: "warn",
|
||||
message: `extension entry unreadable (I/O error): ${params.entryPath}`,
|
||||
source: params.sourceLabel,
|
||||
});
|
||||
return null;
|
||||
},
|
||||
fallback: () => {
|
||||
params.diagnostics.push({
|
||||
level: "error",
|
||||
message: `extension entry escapes package directory: ${params.entryPath}`,
|
||||
source: params.sourceLabel,
|
||||
});
|
||||
return null;
|
||||
},
|
||||
});
|
||||
}
|
||||
const safeSource = opened.path;
|
||||
fs.closeSync(opened.fd);
|
||||
return safeSource;
|
||||
};
|
||||
if (!rejectHardlinks) {
|
||||
const builtCandidate = source.replace(/\.[^.]+$/u, ".js");
|
||||
if (builtCandidate !== source) {
|
||||
candidates.push(builtCandidate);
|
||||
}
|
||||
}
|
||||
|
||||
for (const candidate of new Set(candidates)) {
|
||||
if (!fs.existsSync(candidate)) {
|
||||
continue;
|
||||
}
|
||||
return openCandidate(candidate);
|
||||
}
|
||||
|
||||
return openCandidate(source);
|
||||
}
|
||||
|
||||
function shouldInferBuiltRuntimeEntry(origin: PluginOrigin): boolean {
|
||||
return origin === "config" || origin === "global";
|
||||
}
|
||||
|
||||
function resolveSafePackageEntry(params: {
|
||||
packageDir: string;
|
||||
entryPath: string;
|
||||
sourceLabel: string;
|
||||
diagnostics: PluginDiagnostic[];
|
||||
rejectHardlinks?: boolean;
|
||||
}): { relativePath: string; existingSource?: string } | null {
|
||||
const absolutePath = path.resolve(params.packageDir, params.entryPath);
|
||||
if (fs.existsSync(absolutePath)) {
|
||||
const existingSource = resolvePackageEntrySource({
|
||||
packageDir: params.packageDir,
|
||||
entryPath: params.entryPath,
|
||||
sourceLabel: params.sourceLabel,
|
||||
diagnostics: params.diagnostics,
|
||||
rejectHardlinks: params.rejectHardlinks,
|
||||
});
|
||||
if (!existingSource) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
relativePath: path.relative(params.packageDir, absolutePath).replace(/\\/g, "/"),
|
||||
existingSource,
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
resolveBoundaryPathSync({
|
||||
absolutePath,
|
||||
rootPath: params.packageDir,
|
||||
boundaryLabel: "plugin package directory",
|
||||
});
|
||||
} catch {
|
||||
params.diagnostics.push({
|
||||
level: "error",
|
||||
message: `extension entry escapes package directory: ${params.entryPath}`,
|
||||
source: params.sourceLabel,
|
||||
});
|
||||
return null;
|
||||
}
|
||||
return { relativePath: path.relative(params.packageDir, absolutePath).replace(/\\/g, "/") };
|
||||
}
|
||||
|
||||
function resolveExistingPackageEntrySource(params: {
|
||||
packageDir: string;
|
||||
entryPath: string;
|
||||
sourceLabel: string;
|
||||
diagnostics: PluginDiagnostic[];
|
||||
rejectHardlinks?: boolean;
|
||||
}): string | null {
|
||||
const source = path.resolve(params.packageDir, params.entryPath);
|
||||
if (!fs.existsSync(source)) {
|
||||
return null;
|
||||
}
|
||||
return resolvePackageEntrySource(params);
|
||||
}
|
||||
|
||||
function normalizePackageManifestStringList(value: unknown): string[] {
|
||||
if (!Array.isArray(value)) {
|
||||
return [];
|
||||
}
|
||||
return value.map((entry) => normalizeOptionalString(entry) ?? "").filter(Boolean);
|
||||
}
|
||||
|
||||
function resolvePackageRuntimeEntrySource(params: {
|
||||
packageDir: string;
|
||||
entryPath: string;
|
||||
runtimeEntryPath?: string;
|
||||
origin: PluginOrigin;
|
||||
sourceLabel: string;
|
||||
diagnostics: PluginDiagnostic[];
|
||||
rejectHardlinks?: boolean;
|
||||
}): string | null {
|
||||
const safeEntry = resolveSafePackageEntry({
|
||||
packageDir: params.packageDir,
|
||||
entryPath: params.entryPath,
|
||||
sourceLabel: params.sourceLabel,
|
||||
diagnostics: params.diagnostics,
|
||||
rejectHardlinks: params.rejectHardlinks,
|
||||
});
|
||||
if (!safeEntry) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (params.runtimeEntryPath) {
|
||||
const runtimeSource = resolvePackageEntrySource({
|
||||
packageDir: params.packageDir,
|
||||
entryPath: params.runtimeEntryPath,
|
||||
sourceLabel: params.sourceLabel,
|
||||
diagnostics: params.diagnostics,
|
||||
rejectHardlinks: params.rejectHardlinks,
|
||||
});
|
||||
if (runtimeSource) {
|
||||
return runtimeSource;
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldInferBuiltRuntimeEntry(params.origin)) {
|
||||
for (const candidate of listBuiltRuntimeEntryCandidates(safeEntry.relativePath)) {
|
||||
const runtimeSource = resolveExistingPackageEntrySource({
|
||||
packageDir: params.packageDir,
|
||||
entryPath: candidate,
|
||||
sourceLabel: params.sourceLabel,
|
||||
diagnostics: params.diagnostics,
|
||||
rejectHardlinks: params.rejectHardlinks,
|
||||
});
|
||||
if (runtimeSource) {
|
||||
return runtimeSource;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (safeEntry.existingSource) {
|
||||
return safeEntry.existingSource;
|
||||
}
|
||||
|
||||
return resolvePackageEntrySource({
|
||||
packageDir: params.packageDir,
|
||||
entryPath: params.entryPath,
|
||||
sourceLabel: params.sourceLabel,
|
||||
diagnostics: params.diagnostics,
|
||||
rejectHardlinks: params.rejectHardlinks,
|
||||
});
|
||||
}
|
||||
|
||||
function resolvePackageSetupSource(params: {
|
||||
packageDir: string;
|
||||
manifest: PackageManifest | null;
|
||||
origin: PluginOrigin;
|
||||
sourceLabel: string;
|
||||
diagnostics: PluginDiagnostic[];
|
||||
rejectHardlinks?: boolean;
|
||||
}): string | null {
|
||||
const packageManifest = getPackageManifestMetadata(params.manifest ?? undefined);
|
||||
const setupEntryPath = normalizeOptionalString(packageManifest?.setupEntry);
|
||||
if (!setupEntryPath) {
|
||||
return null;
|
||||
}
|
||||
return resolvePackageRuntimeEntrySource({
|
||||
packageDir: params.packageDir,
|
||||
entryPath: setupEntryPath,
|
||||
runtimeEntryPath: normalizeOptionalString(packageManifest?.runtimeSetupEntry),
|
||||
origin: params.origin,
|
||||
sourceLabel: params.sourceLabel,
|
||||
diagnostics: params.diagnostics,
|
||||
rejectHardlinks: params.rejectHardlinks,
|
||||
});
|
||||
}
|
||||
|
||||
function resolvePackageRuntimeExtensionEntries(params: {
|
||||
packageDir: string;
|
||||
manifest: PackageManifest | null;
|
||||
extensions: readonly string[];
|
||||
origin: PluginOrigin;
|
||||
sourceLabel: string;
|
||||
diagnostics: PluginDiagnostic[];
|
||||
rejectHardlinks?: boolean;
|
||||
}): string[] {
|
||||
const packageManifest = getPackageManifestMetadata(params.manifest ?? undefined);
|
||||
const runtimeExtensions = normalizePackageManifestStringList(packageManifest?.runtimeExtensions);
|
||||
return params.extensions.flatMap((entryPath, index) => {
|
||||
const source = resolvePackageRuntimeEntrySource({
|
||||
packageDir: params.packageDir,
|
||||
entryPath,
|
||||
runtimeEntryPath:
|
||||
runtimeExtensions.length === params.extensions.length
|
||||
? runtimeExtensions[index]
|
||||
: undefined,
|
||||
origin: params.origin,
|
||||
sourceLabel: params.sourceLabel,
|
||||
diagnostics: params.diagnostics,
|
||||
rejectHardlinks: params.rejectHardlinks,
|
||||
});
|
||||
return source ? [source] : [];
|
||||
});
|
||||
}
|
||||
|
||||
function discoverInDirectory(params: {
|
||||
dir: string;
|
||||
origin: PluginOrigin;
|
||||
@@ -871,7 +634,7 @@ function discoverInDirectory(params: {
|
||||
});
|
||||
|
||||
if (extensions.length > 0) {
|
||||
const resolvedRuntimeSources = resolvePackageRuntimeExtensionEntries({
|
||||
const resolvedRuntimeSources = resolvePackageRuntimeExtensionSources({
|
||||
packageDir: fullPath,
|
||||
manifest,
|
||||
extensions,
|
||||
@@ -1007,7 +770,7 @@ function discoverFromPath(params: {
|
||||
});
|
||||
|
||||
if (extensions.length > 0) {
|
||||
const resolvedRuntimeSources = resolvePackageRuntimeExtensionEntries({
|
||||
const resolvedRuntimeSources = resolvePackageRuntimeExtensionSources({
|
||||
packageDir: resolved,
|
||||
manifest,
|
||||
extensions,
|
||||
|
||||
Reference in New Issue
Block a user