fix: preserve install records during registry refresh

This commit is contained in:
Shakker
2026-04-26 00:56:12 +01:00
parent babbad81a9
commit 569d489383
3 changed files with 102 additions and 3 deletions

View File

@@ -254,4 +254,55 @@ describe("installed plugin index persistence", () => {
plugins: [expect.objectContaining({ pluginId: "demo" })],
});
});
it("preserves existing install records when refreshing the manifest cache", async () => {
const stateDir = makeTempDir();
await writePersistedInstalledPluginIndex(
createIndex({
installRecords: {
missing: {
source: "npm",
spec: "missing-plugin@1.0.0",
installPath: path.join(stateDir, "plugins", "missing"),
},
},
plugins: [],
}),
{ stateDir },
);
const index = await refreshPersistedInstalledPluginIndex({
reason: "manual",
stateDir,
candidates: [],
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",
},
});
expect(index).toMatchObject({
installRecords: {
missing: {
source: "npm",
spec: "missing-plugin@1.0.0",
installPath: path.join(stateDir, "plugins", "missing"),
},
},
plugins: [],
});
await expect(readPersistedInstalledPluginIndex({ stateDir })).resolves.toMatchObject({
installRecords: {
missing: {
source: "npm",
spec: "missing-plugin@1.0.0",
installPath: path.join(stateDir, "plugins", "missing"),
},
},
plugins: [],
});
});
});

View File

@@ -198,7 +198,12 @@ export async function inspectPersistedInstalledPluginIndex(
export async function refreshPersistedInstalledPluginIndex(
params: RefreshInstalledPluginIndexParams & InstalledPluginIndexStoreOptions,
): Promise<InstalledPluginIndex> {
const index = refreshInstalledPluginIndex(params);
const persisted = params.installRecords ? null : await readPersistedInstalledPluginIndex(params);
const index = refreshInstalledPluginIndex({
...params,
installRecords:
params.installRecords ?? extractPluginInstallRecordsFromInstalledPluginIndex(persisted),
});
await writePersistedInstalledPluginIndex(index, params);
return index;
}
@@ -206,7 +211,12 @@ export async function refreshPersistedInstalledPluginIndex(
export function refreshPersistedInstalledPluginIndexSync(
params: RefreshInstalledPluginIndexParams & InstalledPluginIndexStoreOptions,
): InstalledPluginIndex {
const index = refreshInstalledPluginIndex(params);
const persisted = params.installRecords ? null : readPersistedInstalledPluginIndexSync(params);
const index = refreshInstalledPluginIndex({
...params,
installRecords:
params.installRecords ?? extractPluginInstallRecordsFromInstalledPluginIndex(persisted),
});
writePersistedInstalledPluginIndexSync(index, params);
return index;
}

View File

@@ -2,7 +2,10 @@ import fs from "node:fs";
import path from "node:path";
import { afterEach, describe, expect, it } from "vitest";
import type { PluginCandidate } from "./discovery.js";
import { writePersistedInstalledPluginIndex } from "./installed-plugin-index-store.js";
import {
readPersistedInstalledPluginIndex,
writePersistedInstalledPluginIndex,
} from "./installed-plugin-index-store.js";
import {
resolveInstalledPluginIndexPolicyHash,
type InstalledPluginIndex,
@@ -385,4 +388,39 @@ describe("plugin registry facade", () => {
},
});
});
it("preserves install records when refreshing the persisted registry", async () => {
const stateDir = makeTempDir();
await writePersistedInstalledPluginIndex(
createIndex("missing", {
installRecords: {
missing: {
source: "npm",
spec: "missing-plugin@1.0.0",
installPath: path.join(stateDir, "plugins", "missing"),
},
},
plugins: [],
}),
{ stateDir },
);
await refreshPluginRegistry({
reason: "manual",
stateDir,
candidates: [],
env: hermeticEnv(),
});
await expect(readPersistedInstalledPluginIndex({ stateDir })).resolves.toMatchObject({
installRecords: {
missing: {
source: "npm",
spec: "missing-plugin@1.0.0",
installPath: path.join(stateDir, "plugins", "missing"),
},
},
plugins: [],
});
});
});