fix(doctor): clean up post-upgrade probe test temp dirs and skip plugins with unreadable package.json

This commit is contained in:
Arnab Saha
2026-05-07 21:51:27 -07:00
committed by Peter Steinberger
parent e0d3c78042
commit 8da6b67607
2 changed files with 97 additions and 81 deletions

View File

@@ -11,93 +11,101 @@ async function makeFixtureRoot(prefix: string): Promise<string> {
describe("runPostUpgradeProbes — plugin.entry_unresolved", () => {
it("flags an enabled plugin whose declared entry does not exist on disk", async () => {
const root = await makeFixtureRoot("entry-unresolved");
const pluginDir = path.join(root, "user-plugins", "ghost");
await fs.mkdir(pluginDir, { recursive: true });
// Plugin package.json declares ./dist/index.js but no dist/.
await fs.writeFile(
path.join(pluginDir, "package.json"),
JSON.stringify({
name: "ghost",
version: "0.0.1",
type: "module",
openclaw: { extensions: ["./dist/index.js"] },
}),
"utf-8",
);
await fs.writeFile(
path.join(pluginDir, "openclaw.plugin.json"),
JSON.stringify({ id: "ghost" }),
"utf-8",
);
try {
const pluginDir = path.join(root, "user-plugins", "ghost");
await fs.mkdir(pluginDir, { recursive: true });
// Plugin package.json declares ./dist/index.js but no dist/.
await fs.writeFile(
path.join(pluginDir, "package.json"),
JSON.stringify({
name: "ghost",
version: "0.0.1",
type: "module",
openclaw: { extensions: ["./dist/index.js"] },
}),
"utf-8",
);
await fs.writeFile(
path.join(pluginDir, "openclaw.plugin.json"),
JSON.stringify({ id: "ghost" }),
"utf-8",
);
const installsPath = path.join(root, "plugins", "installs.json");
await fs.mkdir(path.dirname(installsPath), { recursive: true });
await fs.writeFile(
installsPath,
JSON.stringify({
version: 1,
plugins: [
{
pluginId: "ghost",
manifestPath: path.join(pluginDir, "openclaw.plugin.json"),
rootDir: pluginDir,
enabled: true,
packageJson: { path: "package.json" },
},
],
}),
"utf-8",
);
const installsPath = path.join(root, "plugins", "installs.json");
await fs.mkdir(path.dirname(installsPath), { recursive: true });
await fs.writeFile(
installsPath,
JSON.stringify({
version: 1,
plugins: [
{
pluginId: "ghost",
manifestPath: path.join(pluginDir, "openclaw.plugin.json"),
rootDir: pluginDir,
enabled: true,
packageJson: { path: "package.json" },
},
],
}),
"utf-8",
);
const report = await runPostUpgradeProbes({ installsPath });
const finding = report.findings.find((f) => f.code === "plugin.entry_unresolved");
expect(finding).toBeDefined();
expect(finding?.level).toBe("error");
expect(finding?.plugin).toBe("ghost");
expect(finding?.entry).toBe("./dist/index.js");
const report = await runPostUpgradeProbes({ installsPath });
const finding = report.findings.find((f) => f.code === "plugin.entry_unresolved");
expect(finding).toBeDefined();
expect(finding?.level).toBe("error");
expect(finding?.plugin).toBe("ghost");
expect(finding?.entry).toBe("./dist/index.js");
} finally {
await fs.rm(root, { recursive: true, force: true });
}
});
it("emits no entry_unresolved findings when the entry resolves", async () => {
const root = await makeFixtureRoot("entry-ok");
const pluginDir = path.join(root, "user-plugins", "good");
await fs.mkdir(path.join(pluginDir, "dist"), { recursive: true });
await fs.writeFile(path.join(pluginDir, "dist", "index.js"), "export default {};", "utf-8");
await fs.writeFile(
path.join(pluginDir, "package.json"),
JSON.stringify({
name: "good",
version: "0.0.1",
type: "module",
openclaw: { extensions: ["./dist/index.js"] },
}),
"utf-8",
);
await fs.writeFile(
path.join(pluginDir, "openclaw.plugin.json"),
JSON.stringify({ id: "good" }),
"utf-8",
);
try {
const pluginDir = path.join(root, "user-plugins", "good");
await fs.mkdir(path.join(pluginDir, "dist"), { recursive: true });
await fs.writeFile(path.join(pluginDir, "dist", "index.js"), "export default {};", "utf-8");
await fs.writeFile(
path.join(pluginDir, "package.json"),
JSON.stringify({
name: "good",
version: "0.0.1",
type: "module",
openclaw: { extensions: ["./dist/index.js"] },
}),
"utf-8",
);
await fs.writeFile(
path.join(pluginDir, "openclaw.plugin.json"),
JSON.stringify({ id: "good" }),
"utf-8",
);
const installsPath = path.join(root, "plugins", "installs.json");
await fs.mkdir(path.dirname(installsPath), { recursive: true });
await fs.writeFile(
installsPath,
JSON.stringify({
version: 1,
plugins: [
{
pluginId: "good",
manifestPath: path.join(pluginDir, "openclaw.plugin.json"),
rootDir: pluginDir,
enabled: true,
packageJson: { path: "package.json" },
},
],
}),
"utf-8",
);
const installsPath = path.join(root, "plugins", "installs.json");
await fs.mkdir(path.dirname(installsPath), { recursive: true });
await fs.writeFile(
installsPath,
JSON.stringify({
version: 1,
plugins: [
{
pluginId: "good",
manifestPath: path.join(pluginDir, "openclaw.plugin.json"),
rootDir: pluginDir,
enabled: true,
packageJson: { path: "package.json" },
},
],
}),
"utf-8",
);
const report = await runPostUpgradeProbes({ installsPath });
expect(report.findings.filter((f) => f.code === "plugin.entry_unresolved")).toHaveLength(0);
const report = await runPostUpgradeProbes({ installsPath });
expect(report.findings.filter((f) => f.code === "plugin.entry_unresolved")).toHaveLength(0);
} finally {
await fs.rm(root, { recursive: true, force: true });
}
});
});

View File

@@ -36,7 +36,15 @@ export async function runPostUpgradeProbes(params: {
for (const record of installs.plugins) {
if (!record.enabled) continue;
const pkgRelPath = record.packageJson?.path ?? "package.json";
const pkg = await readInstalledPackageJson(record.rootDir, pkgRelPath);
let pkg: { openclaw?: { extensions?: string[] } };
try {
pkg = await readInstalledPackageJson(record.rootDir, pkgRelPath);
} catch (err) {
process.stderr.write(
`[doctor-post-upgrade] could not read package.json for ${record.pluginId} at ${record.rootDir}: ${err instanceof Error ? err.message : String(err)}\n`,
);
continue;
}
const entries = pkg.openclaw?.extensions ?? [];
for (const entry of entries) {
const absEntry = path.resolve(record.rootDir, entry);