


























@@ -45,6 +45,15 @@ function createPluginCandidate(stateDir: string, pluginId: string): PluginCandid
4545};
4646}
474748+function expectRecordFields(record: unknown, expected: Record<string, unknown>) {
49+expect(record).toBeDefined();
50+const actual = record as Record<string, unknown>;
51+for (const [key, value] of Object.entries(expected)) {
52+expect(actual[key]).toEqual(value);
53+}
54+return actual;
55+}
56+4857afterEach(() => {
4958for (const dir of tempDirs.splice(0)) {
5059fs.rmSync(dir, { recursive: true, force: true });
@@ -73,23 +82,22 @@ describe("plugin index install records store", () => {
73827483const indexPath = resolveInstalledPluginIndexRecordsStorePath({ stateDir });
7584expect(indexPath).toBe(path.join(stateDir, "plugins", "installs.json"));
76-expect(JSON.parse(fs.readFileSync(indexPath, "utf8"))).toMatchObject({
77-version: 1,
78-generatedAtMs: 1777118400000,
79-installRecords: {
80-twitch: {
81-source: "npm",
82-spec: "@openclaw/plugin-twitch@1.0.0",
83-installPath: "plugins/npm/@openclaw/plugin-twitch",
84-},
85-},
86-plugins: [
87-{
88-pluginId: "twitch",
89-installRecordHash: expect.stringMatching(/^[a-f0-9]{64}$/u),
90-},
91-],
85+const persisted = JSON.parse(fs.readFileSync(indexPath, "utf8")) as {
86+version?: number;
87+generatedAtMs?: number;
88+installRecords?: Record<string, unknown>;
89+plugins?: Array<{ pluginId?: string; installRecordHash?: string }>;
90+};
91+expect(persisted.version).toBe(1);
92+expect(persisted.generatedAtMs).toBe(1777118400000);
93+expectRecordFields(persisted.installRecords?.twitch, {
94+source: "npm",
95+spec: "@openclaw/plugin-twitch@1.0.0",
96+installPath: "plugins/npm/@openclaw/plugin-twitch",
9297});
98+expect(persisted.plugins).toHaveLength(1);
99+expect(persisted.plugins?.[0]?.pluginId).toBe("twitch");
100+expect(persisted.plugins?.[0]?.installRecordHash).toMatch(/^[a-f0-9]{64}$/u);
93101await expect(readPersistedInstalledPluginIndexInstallRecords({ stateDir })).resolves.toEqual({
94102twitch: {
95103source: "npm",
@@ -117,20 +125,15 @@ describe("plugin index install records store", () => {
117125},
118126);
119127120-expect(
121-JSON.parse(
122-fs.readFileSync(resolveInstalledPluginIndexRecordsStorePath({ stateDir }), "utf8"),
123-),
124-).toMatchObject({
125-installRecords: {
126-missing: {
127-source: "npm",
128-spec: "missing-plugin@1.0.0",
129-installPath: path.join(stateDir, "plugins", "missing"),
130-},
131-},
132-plugins: [],
128+const persisted = JSON.parse(
129+fs.readFileSync(resolveInstalledPluginIndexRecordsStorePath({ stateDir }), "utf8"),
130+) as { installRecords?: Record<string, unknown>; plugins?: unknown[] };
131+expectRecordFields(persisted.installRecords?.missing, {
132+source: "npm",
133+spec: "missing-plugin@1.0.0",
134+installPath: path.join(stateDir, "plugins", "missing"),
133135});
136+expect(persisted.plugins).toEqual([]);
134137await expect(loadInstalledPluginIndexInstallRecords({ stateDir })).resolves.toEqual({
135138missing: {
136139source: "npm",
@@ -210,36 +213,28 @@ describe("plugin index install records store", () => {
210213fs.mkdirSync(path.dirname(indexPath), { recursive: true });
211214fs.writeFileSync(indexPath, JSON.stringify({ installRecords: {}, plugins: [] }), "utf8");
212215213-await expect(loadInstalledPluginIndexInstallRecords({ stateDir })).resolves.toMatchObject({
214-codex: {
215-source: "npm",
216-spec: "@openclaw/codex@2026.5.2",
217-installPath: codexDir,
218-version: "2026.5.2",
219-resolvedName: "@openclaw/codex",
220-resolvedVersion: "2026.5.2",
221-resolvedSpec: "@openclaw/codex@2026.5.2",
222-},
223-discord: {
224-source: "npm",
225-spec: "@openclaw/discord@2026.5.2",
226-installPath: discordDir,
227-version: "2026.5.2",
228-resolvedName: "@openclaw/discord",
229-resolvedVersion: "2026.5.2",
230-resolvedSpec: "@openclaw/discord@2026.5.2",
231-},
216+const loaded = await loadInstalledPluginIndexInstallRecords({ stateDir });
217+expectRecordFields(loaded.codex, {
218+source: "npm",
219+spec: "@openclaw/codex@2026.5.2",
220+installPath: codexDir,
221+version: "2026.5.2",
222+resolvedName: "@openclaw/codex",
223+resolvedVersion: "2026.5.2",
224+resolvedSpec: "@openclaw/codex@2026.5.2",
232225});
233-expect(loadInstalledPluginIndexInstallRecordsSync({ stateDir })).toMatchObject({
234-codex: {
235-source: "npm",
236-installPath: codexDir,
237-},
238-discord: {
239-source: "npm",
240-installPath: discordDir,
241-},
226+expectRecordFields(loaded.discord, {
227+source: "npm",
228+spec: "@openclaw/discord@2026.5.2",
229+installPath: discordDir,
230+version: "2026.5.2",
231+resolvedName: "@openclaw/discord",
232+resolvedVersion: "2026.5.2",
233+resolvedSpec: "@openclaw/discord@2026.5.2",
242234});
235+const loadedSync = loadInstalledPluginIndexInstallRecordsSync({ stateDir });
236+expectRecordFields(loadedSync.codex, { source: "npm", installPath: codexDir });
237+expectRecordFields(loadedSync.discord, { source: "npm", installPath: discordDir });
243238});
244239245240it("keeps persisted install record metadata over recovered npm records", async () => {
@@ -263,13 +258,12 @@ describe("plugin index install records store", () => {
263258{ stateDir, candidates: [candidate] },
264259);
265260266-await expect(loadInstalledPluginIndexInstallRecords({ stateDir })).resolves.toMatchObject({
267-discord: {
268-source: "npm",
269-spec: "@openclaw/discord@beta",
270-installPath: path.join(stateDir, "custom", "discord"),
271-integrity: "sha512-persisted",
272-},
261+const loaded = await loadInstalledPluginIndexInstallRecords({ stateDir });
262+expectRecordFields(loaded.discord, {
263+source: "npm",
264+spec: "@openclaw/discord@beta",
265+installPath: path.join(stateDir, "custom", "discord"),
266+integrity: "sha512-persisted",
273267});
274268});
275269@@ -290,14 +284,13 @@ describe("plugin index install records store", () => {
290284{ stateDir, candidates: [candidate] },
291285);
292286293-await expect(loadInstalledPluginIndexInstallRecords({ stateDir })).resolves.toMatchObject({
294-"git-demo": {
295-source: "git",
296-spec: "git:file:///tmp/git-demo@abc123",
297-gitUrl: "file:///tmp/git-demo",
298-gitRef: "abc123",
299-gitCommit: "abc123",
300-},
287+const loaded = await loadInstalledPluginIndexInstallRecords({ stateDir });
288+expectRecordFields(loaded["git-demo"], {
289+source: "git",
290+spec: "git:file:///tmp/git-demo@abc123",
291+gitUrl: "file:///tmp/git-demo",
292+gitRef: "abc123",
293+gitCommit: "abc123",
301294});
302295});
303296@@ -329,20 +322,19 @@ describe("plugin index install records store", () => {
329322{ stateDir, candidates: [candidate] },
330323);
331324332-await expect(loadInstalledPluginIndexInstallRecords({ stateDir })).resolves.toMatchObject({
333-"clawpack-demo": {
334-source: "clawhub",
335-spec: "clawhub:clawpack-demo",
336-artifactKind: "npm-pack",
337-artifactFormat: "tgz",
338-npmIntegrity: "sha512-clawpack",
339-npmShasum: "1".repeat(40),
340-npmTarballName: "clawpack-demo-2026.5.1-beta.2.tgz",
341-clawpackSha256: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
342-clawpackSpecVersion: 1,
343-clawpackManifestSha256: "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
344-clawpackSize: 4096,
345-},
325+const loaded = await loadInstalledPluginIndexInstallRecords({ stateDir });
326+expectRecordFields(loaded["clawpack-demo"], {
327+source: "clawhub",
328+spec: "clawhub:clawpack-demo",
329+artifactKind: "npm-pack",
330+artifactFormat: "tgz",
331+npmIntegrity: "sha512-clawpack",
332+npmShasum: "1".repeat(40),
333+npmTarballName: "clawpack-demo-2026.5.1-beta.2.tgz",
334+clawpackSha256: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
335+clawpackSpecVersion: 1,
336+clawpackManifestSha256: "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
337+clawpackSize: 4096,
346338});
347339});
348340@@ -376,7 +368,7 @@ describe("plugin index install records store", () => {
376368spec: "keep@1.0.0",
377369},
378370});
379-expect(withInstall.demo).toMatchObject({
371+expectRecordFields(withInstall.demo, {
380372source: "npm",
381373spec: "demo@latest",
382374installedAt: "2026-04-25T00:00:00.000Z",
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。