openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
175 lines (174 loc) • 7.76 kB
JavaScript
import { c as isRecord } from "./record-coerce-TIsquoVR.mjs";
import { t as hasErrnoCode } from "./errno-FR4CRyya.mjs";
import { n as formatConsoleDiagnosticLine } from "./json-console-line-CVjOh_Ek.mjs";
import { t as VERSION } from "./version-Dx-YMDJx.mjs";
import { f as isOptionalPluginManifestFile } from "./installed-plugin-index-DyE_yB6z.mjs";
import { i as resolvePackageExtensionEntries } from "./package-manifest-zwWxdzub.mjs";
import "./manifest-C1SJczjx.mjs";
import { i as validatePackageExtensionEntriesForInstall } from "./package-entry-resolution-Jjfwwvh1.mjs";
import { r as resolveInstalledPluginIndexInstallOwner } from "./installed-plugin-index-install-owner-CQJv99I6.mjs";
import { n as readPersistedInstalledPluginIndex } from "./installed-plugin-index-store-DrFOypKu.mjs";
import { a as resolvePluginVersionDriftTargets, i as resolvePluginVersionDriftRegistryLag, o as resolvePluginVersionDriftUpdateCommand, t as detectPluginVersionDrift } from "./plugin-version-drift-CUGgMgYa.mjs";
import fs from "node:fs";
import path from "node:path";
import fs$1 from "node:fs/promises";
import crypto from "node:crypto";
//#region src/commands/doctor-post-upgrade.types.ts
/** Probe codes emitted by post-upgrade validation. */
const POST_UPGRADE_PROBE_CODES = [
"plugin.index_unavailable",
"plugin.entry_unresolved",
"plugin.manifest_unavailable",
"plugin.manifest_drift",
"plugin.version_drift"
];
//#endregion
//#region src/commands/doctor-post-upgrade.ts
/** Post-upgrade validation probes for persisted plugin index and package extension entries. */
function buildReport(findings) {
return {
probesRun: [...POST_UPGRADE_PROBE_CODES],
findings
};
}
function isSourceCheckoutPluginRecord(record) {
if (record.origin === "workspace" || record.origin === "config") return true;
return record.origin === "bundled" && isBundledSourceCheckoutPluginRoot(record.rootDir);
}
function isBundledSourceCheckoutPluginRoot(pluginRootDir) {
let current = path.resolve(pluginRootDir);
while (true) {
const extensionsDir = path.dirname(current);
if (path.basename(extensionsDir) === "extensions") {
const packageRoot = path.dirname(extensionsDir);
return fs.existsSync(path.join(packageRoot, ".git")) && fs.existsSync(path.join(packageRoot, "pnpm-workspace.yaml")) && fs.existsSync(path.join(packageRoot, "src"));
}
const next = path.dirname(current);
if (next === current) return false;
current = next;
}
}
async function readInstalledPackageJson(rootDir, packageJsonRelPath) {
const absPath = path.join(rootDir, packageJsonRelPath);
const raw = await fs$1.readFile(absPath, "utf-8");
const parsed = JSON.parse(raw);
if (!isRecord(parsed)) throw new Error("package.json must contain a JSON object");
return parsed;
}
async function resolvePackageJsonRelPath(record) {
if (record.packageJson) return record.packageJson.path;
try {
await fs$1.access(path.join(record.rootDir, "package.json"));
return "package.json";
} catch {
return;
}
}
/** Runs post-upgrade plugin probes and returns structured findings for the caller to render. */
async function runPostUpgradeProbes(params) {
const findings = [];
const installs = await readPersistedInstalledPluginIndex(params);
if (!installs) {
findings.push({
level: "error",
code: "plugin.index_unavailable",
message: "Installed plugin index is missing, unreadable, or malformed. Run `openclaw plugins registry --refresh` to rebuild it before post-upgrade validation."
});
return buildReport(findings);
}
const enabledPlugins = installs.plugins.filter((record) => record.enabled);
const installRecords = Object.fromEntries(Object.entries(installs.installRecords).filter(([id]) => enabledPlugins.some((record) => record.pluginId === id || resolveInstalledPluginIndexInstallOwner(record) === id)));
const drift = await resolvePluginVersionDriftTargets(detectPluginVersionDrift({
gatewayVersion: VERSION,
installRecords,
config: { update: { channel: params.updateChannel } }
}));
for (const entry of drift.drifts) {
const registryLag = resolvePluginVersionDriftRegistryLag(entry);
const updateCommand = resolvePluginVersionDriftUpdateCommand(entry);
const repair = registryLag ? `The registry already serves ${registryLag.registryVersion}; no release reaches ${registryLag.expectedVersion} yet, so no update applies.` : updateCommand ? `Run \`${updateCommand}\`, then restart the Gateway.` : "No confirmed repair target is available; check registry availability and rerun this command.";
findings.push({
level: "warn",
code: "plugin.version_drift",
plugin: entry.pluginId,
message: `Plugin ${entry.pluginId} is ${entry.installedVersion}, but OpenClaw is ${VERSION}. ${repair}`
});
}
for (const record of enabledPlugins) {
const pkgRelPath = await resolvePackageJsonRelPath(record);
if (pkgRelPath) {
let pkg;
try {
pkg = await readInstalledPackageJson(record.rootDir, pkgRelPath);
} catch (err) {
const reason = err instanceof Error ? err.message : String(err);
const message = `[doctor-post-upgrade] could not read package.json for ${record.pluginId} at ${record.rootDir}: ${reason}`;
process.stderr.write(`${formatConsoleDiagnosticLine({
level: "warn",
message
})}\n`);
findings.push({
level: "error",
code: "plugin.entry_unresolved",
message: `Plugin ${record.pluginId}: could not read package.json (${pkgRelPath}): ${reason}. Reinstall the plugin or run \`openclaw plugins registry --refresh\`.`,
plugin: record.pluginId,
entry: pkgRelPath
});
continue;
}
const resolvedEntries = resolvePackageExtensionEntries(pkg);
if (resolvedEntries.status === "invalid") findings.push({
level: "error",
code: "plugin.entry_unresolved",
message: `Plugin ${record.pluginId}: ${resolvedEntries.error}. Reinstall the plugin or run \`openclaw plugins registry --refresh\`.`,
plugin: record.pluginId,
entry: pkgRelPath
});
else if (resolvedEntries.status === "ok") {
const entries = resolvedEntries.entries;
const validation = await validatePackageExtensionEntriesForInstall({
packageDir: record.rootDir,
extensions: [...entries],
manifest: pkg,
allowSourceTypeScriptEntries: isSourceCheckoutPluginRecord(record)
});
if (!validation.ok) {
const offendingEntry = entries.find((entry) => validation.error.includes(entry));
findings.push({
level: "error",
code: "plugin.entry_unresolved",
message: `Plugin ${record.pluginId}: ${validation.error}`,
plugin: record.pluginId,
...offendingEntry ? { entry: offendingEntry } : {}
});
}
}
}
if (record.manifestPath) {
let currentHash;
try {
const raw = await fs$1.readFile(record.manifestPath);
currentHash = crypto.createHash("sha256").update(raw).digest("hex");
} catch (err) {
if (hasErrnoCode(err, "ENOENT") && isOptionalPluginManifestFile(record)) continue;
const reason = err instanceof Error ? err.message : String(err);
findings.push({
level: "error",
code: "plugin.manifest_unavailable",
message: `Plugin ${record.pluginId}: could not read indexed manifest (${record.manifestPath}): ${reason}. Reinstall the plugin or run \`openclaw plugins registry --refresh\`.`,
plugin: record.pluginId
});
continue;
}
if (record.manifestHash && currentHash !== record.manifestHash) findings.push({
level: "warn",
code: "plugin.manifest_drift",
message: `Plugin ${record.pluginId} manifest hash drifted from installs.json snapshot. Run \`openclaw plugins registry --refresh\` to re-sync.`,
plugin: record.pluginId
});
}
}
return buildReport(findings);
}
//#endregion
export { runPostUpgradeProbes };