Files
openclaw/src/plugins/installed-plugin-index.ts
Peter Steinberger ed8f50f240 refactor: simplify plugin dependency handling
Simplify plugin installation and runtime loading around package-manager-owned dependencies, with Jiti reserved for local/TS fallback paths.

Also scans npm plugin install roots so hoisted transitive dependencies are covered by dependency denylist and node_modules symlink checks.
2026-05-01 21:32:22 +01:00

147 lines
5.0 KiB
TypeScript

import type { OpenClawConfig } from "../config/types.js";
import { resolveCompatibilityHostVersion } from "../version.js";
import { normalizePluginsConfig, resolveEffectiveEnableState } from "./config-state.js";
import { normalizeInstallRecordMap } from "./installed-plugin-index-install-records.js";
import {
resolveCompatRegistryVersion,
resolveInstalledPluginIndexPolicyHash,
} from "./installed-plugin-index-policy.js";
import { buildInstalledPluginIndexRecords } from "./installed-plugin-index-record-builder.js";
import { loadInstalledPluginIndexInstallRecordsSync } from "./installed-plugin-index-record-reader.js";
import { resolveInstalledPluginIndexRegistry } from "./installed-plugin-index-registry.js";
import {
INSTALLED_PLUGIN_INDEX_MIGRATION_VERSION,
INSTALLED_PLUGIN_INDEX_VERSION,
INSTALLED_PLUGIN_INDEX_WARNING,
type InstalledPluginIndex,
type InstalledPluginIndexRecord,
type InstalledPluginIndexRefreshReason,
type LoadInstalledPluginIndexParams,
type RefreshInstalledPluginIndexParams,
} from "./installed-plugin-index-types.js";
export {
INSTALLED_PLUGIN_INDEX_MIGRATION_VERSION,
INSTALLED_PLUGIN_INDEX_VERSION,
INSTALLED_PLUGIN_INDEX_WARNING,
} from "./installed-plugin-index-types.js";
export type {
InstalledPluginIndex,
InstalledPluginIndexRecord,
InstalledPluginIndexRefreshReason,
InstalledPluginInstallRecordInfo,
InstalledPluginPackageChannelInfo,
InstalledPluginStartupInfo,
LoadInstalledPluginIndexParams,
RefreshInstalledPluginIndexParams,
} from "./installed-plugin-index-types.js";
export { extractPluginInstallRecordsFromInstalledPluginIndex } from "./installed-plugin-index-install-records.js";
export { diffInstalledPluginIndexInvalidationReasons } from "./installed-plugin-index-invalidation.js";
export { resolveInstalledPluginIndexPolicyHash } from "./installed-plugin-index-policy.js";
function buildInstalledPluginIndex(
params: LoadInstalledPluginIndexParams & { refreshReason?: InstalledPluginIndexRefreshReason },
): InstalledPluginIndex {
const env = params.env ?? process.env;
const { candidates, registry } = resolveInstalledPluginIndexRegistry(params);
const registryDiagnostics = registry.diagnostics ?? [];
const diagnostics = [...registryDiagnostics];
const generatedAtMs = (params.now?.() ?? new Date()).getTime();
const installRecords = normalizeInstallRecordMap(
params.installRecords ??
loadInstalledPluginIndexInstallRecordsSync({
env,
...(params.stateDir ? { stateDir: params.stateDir } : {}),
...(params.pluginIndexFilePath ? { filePath: params.pluginIndexFilePath } : {}),
}),
);
const plugins = buildInstalledPluginIndexRecords({
candidates,
registry,
config: params.config,
diagnostics,
installRecords,
});
return {
version: INSTALLED_PLUGIN_INDEX_VERSION,
warning: INSTALLED_PLUGIN_INDEX_WARNING,
hostContractVersion: resolveCompatibilityHostVersion(env),
compatRegistryVersion: resolveCompatRegistryVersion(),
migrationVersion: INSTALLED_PLUGIN_INDEX_MIGRATION_VERSION,
policyHash: resolveInstalledPluginIndexPolicyHash(params.config),
generatedAtMs,
...(params.refreshReason ? { refreshReason: params.refreshReason } : {}),
installRecords,
plugins,
diagnostics,
};
}
export function loadInstalledPluginIndex(
params: LoadInstalledPluginIndexParams = {},
): InstalledPluginIndex {
return buildInstalledPluginIndex(params);
}
export function refreshInstalledPluginIndex(
params: RefreshInstalledPluginIndexParams,
): InstalledPluginIndex {
return buildInstalledPluginIndex({ ...params, refreshReason: params.reason });
}
export function listInstalledPluginRecords(
index: InstalledPluginIndex,
): readonly InstalledPluginIndexRecord[] {
return index.plugins;
}
export function listEnabledInstalledPluginRecords(
index: InstalledPluginIndex,
config?: OpenClawConfig,
): readonly InstalledPluginIndexRecord[] {
if (!config) {
return index.plugins.filter((plugin) => plugin.enabled);
}
const normalizedConfig = normalizePluginsConfig(config?.plugins);
return index.plugins.filter(
(plugin) =>
resolveEffectiveEnableState({
id: plugin.pluginId,
origin: plugin.origin,
config: normalizedConfig,
rootConfig: config,
enabledByDefault: plugin.enabledByDefault,
}).enabled,
);
}
export function getInstalledPluginRecord(
index: InstalledPluginIndex,
pluginId: string,
): InstalledPluginIndexRecord | undefined {
return index.plugins.find((plugin) => plugin.pluginId === pluginId);
}
export function isInstalledPluginEnabled(
index: InstalledPluginIndex,
pluginId: string,
config?: OpenClawConfig,
): boolean {
const record = getInstalledPluginRecord(index, pluginId);
if (!record) {
return false;
}
if (!config) {
return record.enabled;
}
const normalizedConfig = normalizePluginsConfig(config?.plugins);
return resolveEffectiveEnableState({
id: record.pluginId,
origin: record.origin,
config: normalizedConfig,
rootConfig: config,
enabledByDefault: record.enabledByDefault,
}).enabled;
}