|
1 | 1 | import crypto from "node:crypto"; |
2 | 2 | import fs from "node:fs/promises"; |
3 | 3 | import path from "node:path"; |
| 4 | +import { listBuiltRuntimeEntryCandidates } from "../plugins/package-entrypoints.js"; |
4 | 5 | import type { PostUpgradeFinding, PostUpgradeReport } from "./doctor-post-upgrade.types.js"; |
5 | 6 | |
6 | 7 | type InstalledPluginRecord = { |
@@ -17,7 +18,7 @@ type InstallsJson = { plugins: InstalledPluginRecord[] };
|
17 | 18 | async function readInstalledPackageJson(rootDir: string, packageJsonRelPath: string) { |
18 | 19 | const absPath = path.join(rootDir, packageJsonRelPath); |
19 | 20 | const raw = await fs.readFile(absPath, "utf-8"); |
20 | | -return JSON.parse(raw) as { openclaw?: { extensions?: string[] } }; |
| 21 | +return JSON.parse(raw) as { openclaw?: { extensions?: string[]; runtimeExtensions?: string[] } }; |
21 | 22 | } |
22 | 23 | |
23 | 24 | async function fileExists(absPath: string): Promise<boolean> { |
@@ -48,7 +49,7 @@ export async function runPostUpgradeProbes(params: {
|
48 | 49 | for (const record of installs.plugins) { |
49 | 50 | if (!record.enabled) continue; |
50 | 51 | const pkgRelPath = record.packageJson?.path ?? "package.json"; |
51 | | -let pkg: { openclaw?: { extensions?: string[] } }; |
| 52 | +let pkg: { openclaw?: { extensions?: string[]; runtimeExtensions?: string[] } }; |
52 | 53 | try { |
53 | 54 | pkg = await readInstalledPackageJson(record.rootDir, pkgRelPath); |
54 | 55 | } catch (err) { |
@@ -58,9 +59,31 @@ export async function runPostUpgradeProbes(params: {
|
58 | 59 | continue; |
59 | 60 | } |
60 | 61 | const entries = pkg.openclaw?.extensions ?? []; |
61 | | -for (const entry of entries) { |
| 62 | +const runtimeExtensions = pkg.openclaw?.runtimeExtensions ?? []; |
| 63 | +for (const [index, entry] of entries.entries()) { |
| 64 | +// First, check if there's an explicit runtimeExtensions entry at this index |
| 65 | +const runtimeEntry = runtimeExtensions[index]; |
| 66 | +if (runtimeEntry) { |
| 67 | +const absRuntimeEntry = path.resolve(record.rootDir, runtimeEntry); |
| 68 | +if (await fileExists(absRuntimeEntry)) { |
| 69 | +// Runtime entry exists, so we're good; skip to next entry |
| 70 | +continue; |
| 71 | +} |
| 72 | +// Runtime entry doesn't exist; flag it |
| 73 | +findings.push({ |
| 74 | +level: "error", |
| 75 | +code: "plugin.entry_unresolved", |
| 76 | +message: `Plugin ${record.pluginId} declares runtimeExtensions entry ${runtimeEntry} but the file does not exist at ${absRuntimeEntry}.`, |
| 77 | +plugin: record.pluginId, |
| 78 | +entry: runtimeEntry, |
| 79 | +}); |
| 80 | +continue; |
| 81 | +} |
| 82 | + |
| 83 | +// No explicit runtime entry; try the source entry |
62 | 84 | const absEntry = path.resolve(record.rootDir, entry); |
63 | 85 | if (!(await fileExists(absEntry))) { |
| 86 | +// Source entry doesn't exist either; flag it |
64 | 87 | findings.push({ |
65 | 88 | level: "error", |
66 | 89 | code: "plugin.entry_unresolved", |
|