mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-23 07:01:40 +00:00
refactor: harden outbound, matrix bootstrap, and plugin entry resolution
This commit is contained in:
@@ -4,7 +4,9 @@ import { openBoundaryFileSync } from "../infra/boundary-file-read.js";
|
||||
import { resolveConfigDir, resolveUserPath } from "../utils.js";
|
||||
import { resolveBundledPluginsDir } from "./bundled-dir.js";
|
||||
import {
|
||||
DEFAULT_PLUGIN_ENTRY_CANDIDATES,
|
||||
getPackageManifestMetadata,
|
||||
resolvePackageExtensionEntries,
|
||||
type OpenClawPackageManifest,
|
||||
type PackageManifest,
|
||||
} from "./manifest.js";
|
||||
@@ -243,14 +245,6 @@ function readPackageManifest(dir: string): PackageManifest | null {
|
||||
}
|
||||
}
|
||||
|
||||
function resolvePackageExtensions(manifest: PackageManifest): string[] {
|
||||
const raw = getPackageManifestMetadata(manifest)?.extensions;
|
||||
if (!Array.isArray(raw)) {
|
||||
return [];
|
||||
}
|
||||
return raw.map((entry) => (typeof entry === "string" ? entry.trim() : "")).filter(Boolean);
|
||||
}
|
||||
|
||||
function deriveIdHint(params: {
|
||||
filePath: string;
|
||||
packageName?: string;
|
||||
@@ -394,7 +388,8 @@ function discoverInDirectory(params: {
|
||||
}
|
||||
|
||||
const manifest = readPackageManifest(fullPath);
|
||||
const extensions = manifest ? resolvePackageExtensions(manifest) : [];
|
||||
const extensionResolution = resolvePackageExtensionEntries(manifest ?? undefined);
|
||||
const extensions = extensionResolution.status === "ok" ? extensionResolution.entries : [];
|
||||
|
||||
if (extensions.length > 0) {
|
||||
for (const extPath of extensions) {
|
||||
@@ -428,8 +423,7 @@ function discoverInDirectory(params: {
|
||||
continue;
|
||||
}
|
||||
|
||||
const indexCandidates = ["index.ts", "index.js", "index.mjs", "index.cjs"];
|
||||
const indexFile = indexCandidates
|
||||
const indexFile = [...DEFAULT_PLUGIN_ENTRY_CANDIDATES]
|
||||
.map((candidate) => path.join(fullPath, candidate))
|
||||
.find((candidate) => fs.existsSync(candidate));
|
||||
if (indexFile && isExtensionFile(indexFile)) {
|
||||
@@ -495,7 +489,8 @@ function discoverFromPath(params: {
|
||||
|
||||
if (stat.isDirectory()) {
|
||||
const manifest = readPackageManifest(resolved);
|
||||
const extensions = manifest ? resolvePackageExtensions(manifest) : [];
|
||||
const extensionResolution = resolvePackageExtensionEntries(manifest ?? undefined);
|
||||
const extensions = extensionResolution.status === "ok" ? extensionResolution.entries : [];
|
||||
|
||||
if (extensions.length > 0) {
|
||||
for (const extPath of extensions) {
|
||||
@@ -529,8 +524,7 @@ function discoverFromPath(params: {
|
||||
return;
|
||||
}
|
||||
|
||||
const indexCandidates = ["index.ts", "index.js", "index.mjs", "index.cjs"];
|
||||
const indexFile = indexCandidates
|
||||
const indexFile = [...DEFAULT_PLUGIN_ENTRY_CANDIDATES]
|
||||
.map((candidate) => path.join(resolved, candidate))
|
||||
.find((candidate) => fs.existsSync(candidate));
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { MANIFEST_KEY } from "../compat/legacy-names.js";
|
||||
import { fileExists, readJsonFile, resolveArchiveKind } from "../infra/archive.js";
|
||||
import { writeFileFromPathWithinRoot } from "../infra/fs-safe.js";
|
||||
import { resolveExistingInstallPath, withExtractedArchiveRoot } from "../infra/install-flow.js";
|
||||
@@ -31,18 +30,20 @@ import { validateRegistryNpmSpec } from "../infra/npm-registry-spec.js";
|
||||
import { extensionUsesSkippedScannerPath, isPathInside } from "../security/scan-paths.js";
|
||||
import * as skillScanner from "../security/skill-scanner.js";
|
||||
import { CONFIG_DIR, resolveUserPath } from "../utils.js";
|
||||
import { loadPluginManifest } from "./manifest.js";
|
||||
import {
|
||||
loadPluginManifest,
|
||||
resolvePackageExtensionEntries,
|
||||
type PackageManifest as PluginPackageManifest,
|
||||
} from "./manifest.js";
|
||||
|
||||
type PluginInstallLogger = {
|
||||
info?: (message: string) => void;
|
||||
warn?: (message: string) => void;
|
||||
};
|
||||
|
||||
type PackageManifest = {
|
||||
name?: string;
|
||||
version?: string;
|
||||
type PackageManifest = PluginPackageManifest & {
|
||||
dependencies?: Record<string, string>;
|
||||
} & Partial<Record<typeof MANIFEST_KEY, { extensions?: string[] }>>;
|
||||
};
|
||||
|
||||
const MISSING_EXTENSIONS_ERROR =
|
||||
'package.json missing openclaw.extensions; update the plugin package to include openclaw.extensions (for example ["./dist/index.js"]). See https://docs.openclaw.ai/help/troubleshooting#plugin-install-fails-with-missing-openclaw-extensions';
|
||||
@@ -86,15 +87,14 @@ function validatePluginId(pluginId: string): string | null {
|
||||
}
|
||||
|
||||
function ensureOpenClawExtensions(params: { manifest: PackageManifest }): string[] {
|
||||
const extensions = params.manifest[MANIFEST_KEY]?.extensions;
|
||||
if (!Array.isArray(extensions)) {
|
||||
const resolved = resolvePackageExtensionEntries(params.manifest);
|
||||
if (resolved.status === "missing") {
|
||||
throw new Error(MISSING_EXTENSIONS_ERROR);
|
||||
}
|
||||
const list = extensions.map((e) => (typeof e === "string" ? e.trim() : "")).filter(Boolean);
|
||||
if (list.length === 0) {
|
||||
if (resolved.status === "empty") {
|
||||
throw new Error("package.json openclaw.extensions is empty");
|
||||
}
|
||||
return list;
|
||||
return resolved.entries;
|
||||
}
|
||||
|
||||
function buildFileInstallResult(pluginId: string, targetFile: string): InstallPluginResult {
|
||||
|
||||
@@ -148,6 +148,18 @@ export type OpenClawPackageManifest = {
|
||||
install?: PluginPackageInstall;
|
||||
};
|
||||
|
||||
export const DEFAULT_PLUGIN_ENTRY_CANDIDATES = [
|
||||
"index.ts",
|
||||
"index.js",
|
||||
"index.mjs",
|
||||
"index.cjs",
|
||||
] as const;
|
||||
|
||||
export type PackageExtensionResolution =
|
||||
| { status: "ok"; entries: string[] }
|
||||
| { status: "missing"; entries: [] }
|
||||
| { status: "empty"; entries: [] };
|
||||
|
||||
export type ManifestKey = typeof MANIFEST_KEY;
|
||||
|
||||
export type PackageManifest = {
|
||||
@@ -164,3 +176,19 @@ export function getPackageManifestMetadata(
|
||||
}
|
||||
return manifest[MANIFEST_KEY];
|
||||
}
|
||||
|
||||
export function resolvePackageExtensionEntries(
|
||||
manifest: PackageManifest | undefined,
|
||||
): PackageExtensionResolution {
|
||||
const raw = getPackageManifestMetadata(manifest)?.extensions;
|
||||
if (!Array.isArray(raw)) {
|
||||
return { status: "missing", entries: [] };
|
||||
}
|
||||
const entries = raw
|
||||
.map((entry) => (typeof entry === "string" ? entry.trim() : ""))
|
||||
.filter(Boolean);
|
||||
if (entries.length === 0) {
|
||||
return { status: "empty", entries: [] };
|
||||
}
|
||||
return { status: "ok", entries };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user