mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-25 02:49:35 +00:00
129 lines
4.2 KiB
TypeScript
129 lines
4.2 KiB
TypeScript
// Detects plugin version drift between config, manifests, and installs.
|
|
import type { OpenClawConfig } from "../config/types.js";
|
|
import type { PluginInstallRecord } from "../config/types.plugins.js";
|
|
import { parseClawHubPluginSpec } from "../infra/clawhub-spec.js";
|
|
import { parseRegistryNpmSpec } from "../infra/npm-registry-spec.js";
|
|
import { normalizePluginsConfig, resolveEffectiveEnableState } from "./config-state.js";
|
|
import {
|
|
resolveTrustedSourceLinkedOfficialClawHubInstall,
|
|
resolveTrustedSourceLinkedOfficialNpmSpec,
|
|
} from "./official-external-install-records.js";
|
|
|
|
export type PluginVersionDriftEntry = {
|
|
pluginId: string;
|
|
installedVersion: string;
|
|
gatewayVersion: string;
|
|
source: PluginInstallRecord["source"];
|
|
packageName?: string;
|
|
spec?: string;
|
|
};
|
|
|
|
export type PluginVersionDriftReport = {
|
|
gatewayVersion: string;
|
|
drifts: PluginVersionDriftEntry[];
|
|
};
|
|
|
|
/**
|
|
* Strip a trailing build qualifier (e.g. `2026.5.4-1` -> `2026.5.4`) so that
|
|
* a gateway packaged as `2026.5.4-1` is not reported as drifted from a
|
|
* plugin packaged as `2026.5.4`. Both ends are normalized identically.
|
|
*/
|
|
function normalizeVersion(value: string): string {
|
|
return value.replace(/-\d+$/, "");
|
|
}
|
|
|
|
function isPluginEnabled(config: OpenClawConfig | undefined, pluginId: string): boolean {
|
|
const normalizedPluginConfig = normalizePluginsConfig(config?.plugins);
|
|
return resolveEffectiveEnableState({
|
|
id: pluginId,
|
|
origin: "global",
|
|
config: normalizedPluginConfig,
|
|
rootConfig: config,
|
|
}).enabled;
|
|
}
|
|
|
|
function shouldCompareOfficialInstallToGateway(params: {
|
|
pluginId: string;
|
|
record: PluginInstallRecord;
|
|
}): boolean {
|
|
const officialNpmSpec = resolveTrustedSourceLinkedOfficialNpmSpec(params);
|
|
if (officialNpmSpec) {
|
|
return parseRegistryNpmSpec(officialNpmSpec)?.selectorKind !== "exact-version";
|
|
}
|
|
const officialClawHubInstall = resolveTrustedSourceLinkedOfficialClawHubInstall(params);
|
|
if (officialClawHubInstall) {
|
|
if (officialClawHubInstall.clawhubSpec) {
|
|
return !parseClawHubPluginSpec(officialClawHubInstall.clawhubSpec)?.version;
|
|
}
|
|
return (
|
|
parseRegistryNpmSpec(officialClawHubInstall.npmSpec ?? "")?.selectorKind !== "exact-version"
|
|
);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Compare active official external plugin installs against the running gateway
|
|
* version and return any mismatches.
|
|
*
|
|
* @param params.gatewayVersion The gateway version string (typically the
|
|
* `version` field of the installed openclaw package.json).
|
|
* @param params.installRecords The full set of recorded plugin installs (as
|
|
* produced by `loadInstalledPluginIndexInstallRecords`).
|
|
* @param params.config The merged daemon-side OpenClawConfig (optional).
|
|
* Plugins inactive under the effective activation policy are skipped.
|
|
*
|
|
* The returned `drifts` list is sorted by `pluginId` for stable output.
|
|
*/
|
|
export function detectPluginVersionDrift(params: {
|
|
gatewayVersion: string;
|
|
installRecords: Record<string, PluginInstallRecord>;
|
|
config?: OpenClawConfig;
|
|
}): PluginVersionDriftReport {
|
|
const { gatewayVersion, installRecords, config } = params;
|
|
const normalizedGateway = normalizeVersion(gatewayVersion);
|
|
const drifts: PluginVersionDriftEntry[] = [];
|
|
|
|
for (const [pluginId, record] of Object.entries(installRecords)) {
|
|
if (!record) {
|
|
continue;
|
|
}
|
|
if (!isPluginEnabled(config, pluginId)) {
|
|
continue;
|
|
}
|
|
if (
|
|
!shouldCompareOfficialInstallToGateway({
|
|
pluginId,
|
|
record,
|
|
})
|
|
) {
|
|
continue;
|
|
}
|
|
const installedVersion = record.resolvedVersion ?? record.version;
|
|
if (!installedVersion) {
|
|
// No version recorded for this install — nothing to compare against.
|
|
// Don't fabricate drift; surface tooling (status.print) can flag this
|
|
// separately if desired.
|
|
continue;
|
|
}
|
|
if (normalizeVersion(installedVersion) === normalizedGateway) {
|
|
continue;
|
|
}
|
|
drifts.push({
|
|
pluginId,
|
|
installedVersion,
|
|
gatewayVersion,
|
|
source: record.source,
|
|
...(record.resolvedName ? { packageName: record.resolvedName } : {}),
|
|
...(record.spec ? { spec: record.spec } : {}),
|
|
});
|
|
}
|
|
|
|
drifts.sort((a, b) => a.pluginId.localeCompare(b.pluginId));
|
|
|
|
return {
|
|
gatewayVersion,
|
|
drifts,
|
|
};
|
|
}
|