mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 10:40:43 +00:00
feat(plugins): inspect persisted plugin index state
This commit is contained in:
@@ -3,6 +3,7 @@ import path from "node:path";
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import type { PluginCandidate } from "./discovery.js";
|
||||
import {
|
||||
inspectPersistedInstalledPluginIndex,
|
||||
readPersistedInstalledPluginIndex,
|
||||
refreshPersistedInstalledPluginIndex,
|
||||
resolveInstalledPluginIndexStorePath,
|
||||
@@ -111,6 +112,76 @@ describe("installed plugin index persistence", () => {
|
||||
await expect(readPersistedInstalledPluginIndex({ stateDir })).resolves.toBeNull();
|
||||
});
|
||||
|
||||
it("inspects missing, fresh, and stale persisted index state without loading runtime", async () => {
|
||||
const stateDir = makeTempDir();
|
||||
const pluginDir = path.join(stateDir, "plugins", "demo");
|
||||
fs.mkdirSync(pluginDir, { recursive: true });
|
||||
const candidate = createCandidate(pluginDir);
|
||||
const env = {
|
||||
OPENCLAW_BUNDLED_PLUGINS_DIR: undefined,
|
||||
OPENCLAW_DISABLE_PLUGIN_DISCOVERY_CACHE: "1",
|
||||
OPENCLAW_DISABLE_PLUGIN_MANIFEST_CACHE: "1",
|
||||
OPENCLAW_VERSION: "2026.4.25",
|
||||
VITEST: "true",
|
||||
};
|
||||
|
||||
await expect(
|
||||
inspectPersistedInstalledPluginIndex({ stateDir, candidates: [candidate], env }),
|
||||
).resolves.toMatchObject({
|
||||
state: "missing",
|
||||
refreshReasons: ["missing"],
|
||||
persisted: null,
|
||||
current: {
|
||||
plugins: [expect.objectContaining({ pluginId: "demo" })],
|
||||
},
|
||||
});
|
||||
|
||||
const current = await refreshPersistedInstalledPluginIndex({
|
||||
reason: "manual",
|
||||
stateDir,
|
||||
candidates: [candidate],
|
||||
env,
|
||||
});
|
||||
|
||||
await expect(
|
||||
inspectPersistedInstalledPluginIndex({ stateDir, candidates: [candidate], env }),
|
||||
).resolves.toMatchObject({
|
||||
state: "fresh",
|
||||
refreshReasons: [],
|
||||
persisted: current,
|
||||
current: {
|
||||
plugins: [expect.objectContaining({ pluginId: "demo" })],
|
||||
},
|
||||
});
|
||||
|
||||
fs.writeFileSync(
|
||||
path.join(pluginDir, "openclaw.plugin.json"),
|
||||
JSON.stringify({
|
||||
id: "demo",
|
||||
name: "Demo",
|
||||
configSchema: { type: "object" },
|
||||
providers: ["demo", "demo-next"],
|
||||
}),
|
||||
"utf8",
|
||||
);
|
||||
|
||||
await expect(
|
||||
inspectPersistedInstalledPluginIndex({ stateDir, candidates: [candidate], env }),
|
||||
).resolves.toMatchObject({
|
||||
state: "stale",
|
||||
refreshReasons: ["stale-manifest"],
|
||||
persisted: current,
|
||||
current: {
|
||||
plugins: [
|
||||
expect.objectContaining({
|
||||
pluginId: "demo",
|
||||
contributions: expect.objectContaining({ providers: ["demo", "demo-next"] }),
|
||||
}),
|
||||
],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("refreshes and persists a rebuilt index without loading plugin runtime", async () => {
|
||||
const stateDir = makeTempDir();
|
||||
const pluginDir = path.join(stateDir, "plugins", "demo");
|
||||
|
||||
@@ -4,13 +4,17 @@ import { resolveStateDir } from "../config/paths.js";
|
||||
import { readJsonFile, writeJsonAtomic } from "../infra/json-files.js";
|
||||
import { safeParseWithSchema } from "../utils/zod-parse.js";
|
||||
import {
|
||||
diffInstalledPluginIndexInvalidationReasons,
|
||||
INSTALLED_PLUGIN_INDEX_VERSION,
|
||||
loadInstalledPluginIndex,
|
||||
refreshInstalledPluginIndex,
|
||||
type InstalledPluginIndex,
|
||||
type InstalledPluginIndexRefreshReason,
|
||||
type LoadInstalledPluginIndexParams,
|
||||
type RefreshInstalledPluginIndexParams,
|
||||
} from "./installed-plugin-index.js";
|
||||
|
||||
const INSTALLED_PLUGIN_INDEX_STORE_PATH = path.join("plugins", "installed-index.json");
|
||||
export const INSTALLED_PLUGIN_INDEX_STORE_PATH = path.join("plugins", "installed-index.json");
|
||||
|
||||
export type InstalledPluginIndexStoreOptions = {
|
||||
env?: NodeJS.ProcessEnv;
|
||||
@@ -18,6 +22,15 @@ export type InstalledPluginIndexStoreOptions = {
|
||||
filePath?: string;
|
||||
};
|
||||
|
||||
export type InstalledPluginIndexStoreState = "missing" | "fresh" | "stale";
|
||||
|
||||
export type InstalledPluginIndexStoreInspection = {
|
||||
state: InstalledPluginIndexStoreState;
|
||||
refreshReasons: readonly InstalledPluginIndexRefreshReason[];
|
||||
persisted: InstalledPluginIndex | null;
|
||||
current: InstalledPluginIndex;
|
||||
};
|
||||
|
||||
const ContributionArraySchema = z.array(z.string());
|
||||
|
||||
const InstalledPluginIndexContributionsSchema = z
|
||||
@@ -109,6 +122,29 @@ export async function writePersistedInstalledPluginIndex(
|
||||
return filePath;
|
||||
}
|
||||
|
||||
export async function inspectPersistedInstalledPluginIndex(
|
||||
params: LoadInstalledPluginIndexParams & InstalledPluginIndexStoreOptions = {},
|
||||
): Promise<InstalledPluginIndexStoreInspection> {
|
||||
const persisted = await readPersistedInstalledPluginIndex(params);
|
||||
const current = loadInstalledPluginIndex(params);
|
||||
if (!persisted) {
|
||||
return {
|
||||
state: "missing",
|
||||
refreshReasons: ["missing"],
|
||||
persisted: null,
|
||||
current,
|
||||
};
|
||||
}
|
||||
|
||||
const refreshReasons = diffInstalledPluginIndexInvalidationReasons(persisted, current);
|
||||
return {
|
||||
state: refreshReasons.length > 0 ? "stale" : "fresh",
|
||||
refreshReasons,
|
||||
persisted,
|
||||
current,
|
||||
};
|
||||
}
|
||||
|
||||
export async function refreshPersistedInstalledPluginIndex(
|
||||
params: RefreshInstalledPluginIndexParams & InstalledPluginIndexStoreOptions,
|
||||
): Promise<InstalledPluginIndex> {
|
||||
|
||||
Reference in New Issue
Block a user