From 9e086d6ed833900dd1cd07bf5b0fb4436a559f80 Mon Sep 17 00:00:00 2001 From: Shakker Date: Sat, 25 Apr 2026 23:10:11 +0100 Subject: [PATCH] refactor: split plugin index record reader --- .../installed-plugin-index-record-reader.ts | 58 +++++++++++++++++ src/plugins/installed-plugin-index-records.ts | 63 +++++-------------- .../installed-plugin-index-store-path.ts | 21 +++++++ src/plugins/installed-plugin-index-store.ts | 30 +++------ src/plugins/manifest-registry.ts | 2 +- 5 files changed, 104 insertions(+), 70 deletions(-) create mode 100644 src/plugins/installed-plugin-index-record-reader.ts create mode 100644 src/plugins/installed-plugin-index-store-path.ts diff --git a/src/plugins/installed-plugin-index-record-reader.ts b/src/plugins/installed-plugin-index-record-reader.ts new file mode 100644 index 00000000000..608d7d6d06c --- /dev/null +++ b/src/plugins/installed-plugin-index-record-reader.ts @@ -0,0 +1,58 @@ +import type { PluginInstallRecord } from "../config/types.plugins.js"; +import { readJsonFile, readJsonFileSync } from "../infra/json-files.js"; +import { + resolveInstalledPluginIndexStorePath, + type InstalledPluginIndexStoreOptions, +} from "./installed-plugin-index-store-path.js"; + +function isRecord(value: unknown): value is Record { + return typeof value === "object" && value !== null && !Array.isArray(value); +} + +function cloneInstallRecords( + records: Record | undefined, +): Record { + return structuredClone(records ?? {}); +} + +export function extractPluginInstallRecordsFromPersistedInstalledPluginIndex( + index: unknown, +): Record | null { + if (!isRecord(index) || !Array.isArray(index.plugins)) { + return null; + } + const records: Record = {}; + for (const entry of index.plugins) { + if (!isRecord(entry) || typeof entry.pluginId !== "string" || !isRecord(entry.installRecord)) { + continue; + } + records[entry.pluginId] = structuredClone(entry.installRecord) as PluginInstallRecord; + } + return records; +} + +export async function readPersistedInstalledPluginIndexInstallRecords( + options: InstalledPluginIndexStoreOptions = {}, +): Promise | null> { + const parsed = await readJsonFile(resolveInstalledPluginIndexStorePath(options)); + return extractPluginInstallRecordsFromPersistedInstalledPluginIndex(parsed); +} + +export function readPersistedInstalledPluginIndexInstallRecordsSync( + options: InstalledPluginIndexStoreOptions = {}, +): Record | null { + const parsed = readJsonFileSync(resolveInstalledPluginIndexStorePath(options)); + return extractPluginInstallRecordsFromPersistedInstalledPluginIndex(parsed); +} + +export async function loadInstalledPluginIndexInstallRecords( + params: InstalledPluginIndexStoreOptions = {}, +): Promise> { + return cloneInstallRecords((await readPersistedInstalledPluginIndexInstallRecords(params)) ?? {}); +} + +export function loadInstalledPluginIndexInstallRecordsSync( + params: InstalledPluginIndexStoreOptions = {}, +): Record { + return cloneInstallRecords(readPersistedInstalledPluginIndexInstallRecordsSync(params) ?? {}); +} diff --git a/src/plugins/installed-plugin-index-records.ts b/src/plugins/installed-plugin-index-records.ts index c4a71409f60..2452c81195a 100644 --- a/src/plugins/installed-plugin-index-records.ts +++ b/src/plugins/installed-plugin-index-records.ts @@ -1,17 +1,23 @@ import type { OpenClawConfig } from "../config/types.openclaw.js"; import type { PluginInstallRecord } from "../config/types.plugins.js"; import { - readPersistedInstalledPluginIndex, - readPersistedInstalledPluginIndexSync, - refreshPersistedInstalledPluginIndex, - resolveInstalledPluginIndexStorePath, -} from "./installed-plugin-index-store.js"; -import { - extractPluginInstallRecordsFromInstalledPluginIndex, - type RefreshInstalledPluginIndexParams, -} from "./installed-plugin-index.js"; + loadInstalledPluginIndexInstallRecords, + loadInstalledPluginIndexInstallRecordsSync, + readPersistedInstalledPluginIndexInstallRecords, + readPersistedInstalledPluginIndexInstallRecordsSync, +} from "./installed-plugin-index-record-reader.js"; +import { resolveInstalledPluginIndexStorePath } from "./installed-plugin-index-store-path.js"; +import { refreshPersistedInstalledPluginIndex } from "./installed-plugin-index-store.js"; +import { type RefreshInstalledPluginIndexParams } from "./installed-plugin-index.js"; import { recordPluginInstall, type PluginInstallUpdate } from "./installs.js"; +export { + loadInstalledPluginIndexInstallRecords, + loadInstalledPluginIndexInstallRecordsSync, + readPersistedInstalledPluginIndexInstallRecords, + readPersistedInstalledPluginIndexInstallRecordsSync, +}; + export const PLUGIN_INSTALLS_CONFIG_PATH = ["plugins", "installs"] as const; export type InstalledPluginIndexRecordStoreOptions = { @@ -25,39 +31,12 @@ type InstalledPluginIndexRecordRefreshOptions = InstalledPluginIndexRecordStoreO now?: () => Date; }; -function toInstallRecords( - index: Awaited>, -): Record | null { - if (!index) { - return null; - } - return extractPluginInstallRecordsFromInstalledPluginIndex(index); -} - -function cloneInstallRecords( - records: Record | undefined, -): Record { - return structuredClone(records ?? {}); -} - export function resolveInstalledPluginIndexRecordsStorePath( options: InstalledPluginIndexRecordStoreOptions = {}, ): string { return resolveInstalledPluginIndexStorePath(options); } -export async function readPersistedInstalledPluginIndexInstallRecords( - options: InstalledPluginIndexRecordStoreOptions = {}, -): Promise | null> { - return toInstallRecords(await readPersistedInstalledPluginIndex(options)); -} - -export function readPersistedInstalledPluginIndexInstallRecordsSync( - options: InstalledPluginIndexRecordStoreOptions = {}, -): Record | null { - return toInstallRecords(readPersistedInstalledPluginIndexSync(options)); -} - export async function writePersistedInstalledPluginIndexInstallRecords( records: Record, options: InstalledPluginIndexRecordRefreshOptions = {}, @@ -70,18 +49,6 @@ export async function writePersistedInstalledPluginIndexInstallRecords( return resolveInstalledPluginIndexRecordsStorePath(options); } -export async function loadInstalledPluginIndexInstallRecords( - params: InstalledPluginIndexRecordStoreOptions = {}, -): Promise> { - return cloneInstallRecords((await readPersistedInstalledPluginIndexInstallRecords(params)) ?? {}); -} - -export function loadInstalledPluginIndexInstallRecordsSync( - params: InstalledPluginIndexRecordStoreOptions = {}, -): Record { - return cloneInstallRecords(readPersistedInstalledPluginIndexInstallRecordsSync(params) ?? {}); -} - export function withPluginInstallRecords( config: OpenClawConfig, records: Record, diff --git a/src/plugins/installed-plugin-index-store-path.ts b/src/plugins/installed-plugin-index-store-path.ts new file mode 100644 index 00000000000..33941f95fd5 --- /dev/null +++ b/src/plugins/installed-plugin-index-store-path.ts @@ -0,0 +1,21 @@ +import path from "node:path"; +import { resolveStateDir } from "../config/paths.js"; + +export const INSTALLED_PLUGIN_INDEX_STORE_PATH = path.join("plugins", "installs.json"); + +export type InstalledPluginIndexStoreOptions = { + env?: NodeJS.ProcessEnv; + stateDir?: string; + filePath?: string; +}; + +export function resolveInstalledPluginIndexStorePath( + options: InstalledPluginIndexStoreOptions = {}, +): string { + if (options.filePath) { + return options.filePath; + } + const env = options.env ?? process.env; + const stateDir = options.stateDir ?? resolveStateDir(env); + return path.join(stateDir, INSTALLED_PLUGIN_INDEX_STORE_PATH); +} diff --git a/src/plugins/installed-plugin-index-store.ts b/src/plugins/installed-plugin-index-store.ts index 49784e3989d..bce1eb9723e 100644 --- a/src/plugins/installed-plugin-index-store.ts +++ b/src/plugins/installed-plugin-index-store.ts @@ -1,8 +1,10 @@ -import path from "node:path"; import { z } from "zod"; -import { resolveStateDir } from "../config/paths.js"; import { readJsonFile, readJsonFileSync, writeJsonAtomic } from "../infra/json-files.js"; import { safeParseWithSchema } from "../utils/zod-parse.js"; +import { + resolveInstalledPluginIndexStorePath, + type InstalledPluginIndexStoreOptions, +} from "./installed-plugin-index-store-path.js"; import { diffInstalledPluginIndexInvalidationReasons, extractPluginInstallRecordsFromInstalledPluginIndex, @@ -16,14 +18,11 @@ import { type LoadInstalledPluginIndexParams, type RefreshInstalledPluginIndexParams, } from "./installed-plugin-index.js"; - -export const INSTALLED_PLUGIN_INDEX_STORE_PATH = path.join("plugins", "installs.json"); - -export type InstalledPluginIndexStoreOptions = { - env?: NodeJS.ProcessEnv; - stateDir?: string; - filePath?: string; -}; +export { + INSTALLED_PLUGIN_INDEX_STORE_PATH, + resolveInstalledPluginIndexStorePath, + type InstalledPluginIndexStoreOptions, +} from "./installed-plugin-index-store-path.js"; export type InstalledPluginIndexStoreState = "missing" | "fresh" | "stale"; @@ -112,17 +111,6 @@ function parseInstalledPluginIndex(value: unknown): InstalledPluginIndex | null return safeParseWithSchema(InstalledPluginIndexSchema, value) as InstalledPluginIndex | null; } -export function resolveInstalledPluginIndexStorePath( - options: InstalledPluginIndexStoreOptions = {}, -): string { - if (options.filePath) { - return options.filePath; - } - const env = options.env ?? process.env; - const stateDir = options.stateDir ?? resolveStateDir(env); - return path.join(stateDir, INSTALLED_PLUGIN_INDEX_STORE_PATH); -} - export async function readPersistedInstalledPluginIndex( options: InstalledPluginIndexStoreOptions = {}, ): Promise { diff --git a/src/plugins/manifest-registry.ts b/src/plugins/manifest-registry.ts index 75635c6e1eb..2eab529364e 100644 --- a/src/plugins/manifest-registry.ts +++ b/src/plugins/manifest-registry.ts @@ -16,7 +16,7 @@ import { type NormalizedPluginsConfig, } from "./config-policy.js"; import { discoverOpenClawPlugins, type PluginCandidate } from "./discovery.js"; -import { loadInstalledPluginIndexInstallRecordsSync } from "./installed-plugin-index-records.js"; +import { loadInstalledPluginIndexInstallRecordsSync } from "./installed-plugin-index-record-reader.js"; import type { PluginManifestCommandAlias } from "./manifest-command-aliases.js"; import { clearPluginManifestRegistryCache,