























11import crypto from "node:crypto";
22import fs from "node:fs/promises";
33import path from "node:path";
4-import { type PackageManifest } from "../plugins/manifest.js";
4+import type { PackageManifest } from "../plugins/manifest.js";
55import { validatePackageExtensionEntriesForInstall } from "../plugins/package-entry-resolution.js";
6-import type { PostUpgradeFinding, PostUpgradeReport } from "./doctor-post-upgrade.types.js";
6+import {
7+POST_UPGRADE_PROBE_CODES,
8+type PostUpgradeFinding,
9+type PostUpgradeReport,
10+} from "./doctor-post-upgrade.types.js";
711812type InstalledPluginRecord = {
913pluginId: string;
@@ -16,6 +20,57 @@ type InstalledPluginRecord = {
16201721type InstallsJson = { plugins: InstalledPluginRecord[] };
182223+function buildReport(findings: PostUpgradeFinding[]): PostUpgradeReport {
24+return { probesRun: [...POST_UPGRADE_PROBE_CODES], findings };
25+}
26+27+function isInstallsJson(value: unknown): value is InstallsJson {
28+return (
29+typeof value === "object" &&
30+value !== null &&
31+Array.isArray((value as { plugins?: unknown }).plugins) &&
32+(value as { plugins: unknown[] }).plugins.every(isInstalledPluginRecord)
33+);
34+}
35+36+function isOptionalString(value: unknown): value is string | undefined {
37+return value === undefined || typeof value === "string";
38+}
39+40+function isPackageJsonRef(value: unknown): value is InstalledPluginRecord["packageJson"] {
41+return (
42+value === undefined ||
43+(typeof value === "object" &&
44+value !== null &&
45+typeof (value as { path?: unknown }).path === "string")
46+);
47+}
48+49+function isInstalledPluginRecord(value: unknown): value is InstalledPluginRecord {
50+if (typeof value !== "object" || value === null) {
51+return false;
52+}
53+const record = value as InstalledPluginRecord;
54+return (
55+typeof record.pluginId === "string" &&
56+typeof record.rootDir === "string" &&
57+typeof record.enabled === "boolean" &&
58+isPackageJsonRef(record.packageJson) &&
59+isOptionalString(record.manifestPath) &&
60+isOptionalString(record.manifestHash)
61+);
62+}
63+64+async function readInstallsJson(installsPath: string): Promise<InstallsJson | null> {
65+try {
66+const installsRaw = await fs.readFile(installsPath, "utf-8");
67+const installs = JSON.parse(installsRaw) as unknown;
68+return isInstallsJson(installs) ? installs : null;
69+} catch {
70+return null;
71+}
72+}
73+1974async function readInstalledPackageJson(
2075rootDir: string,
2176packageJsonRelPath: string,
@@ -38,8 +93,16 @@ export async function runPostUpgradeProbes(params: {
3893installsPath: string;
3994}): Promise<PostUpgradeReport> {
4095const findings: PostUpgradeFinding[] = [];
41-const installsRaw = await fs.readFile(params.installsPath, "utf-8");
42-const installs = JSON.parse(installsRaw) as InstallsJson;
96+const installs = await readInstallsJson(params.installsPath);
97+if (!installs) {
98+findings.push({
99+level: "error",
100+code: "plugin.index_unavailable",
101+message:
102+"Installed plugin index is missing, unreadable, or malformed. Run `openclaw plugins registry --refresh` to rebuild it before post-upgrade validation.",
103+});
104+return buildReport(findings);
105+}
4310644107for (const record of installs.plugins) {
45108if (!record.enabled) {
@@ -90,5 +153,5 @@ export async function runPostUpgradeProbes(params: {
90153}
91154}
9215593-return { probesRun: ["plugin.entry_unresolved", "plugin.manifest_drift"], findings };
156+return buildReport(findings);
94157}
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。