refactor: share plugin package entry resolution

This commit is contained in:
Peter Steinberger
2026-04-26 11:11:41 +01:00
parent 8ba9c9098a
commit f337c9019c
6 changed files with 663 additions and 477 deletions

View File

@@ -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,