refactor: split plugin index record reader

This commit is contained in:
Shakker
2026-04-25 23:10:11 +01:00
parent 57c4279c4a
commit 9e086d6ed8
5 changed files with 104 additions and 70 deletions

View File

@@ -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<string, unknown> {
return typeof value === "object" && value !== null && !Array.isArray(value);
}
function cloneInstallRecords(
records: Record<string, PluginInstallRecord> | undefined,
): Record<string, PluginInstallRecord> {
return structuredClone(records ?? {});
}
export function extractPluginInstallRecordsFromPersistedInstalledPluginIndex(
index: unknown,
): Record<string, PluginInstallRecord> | null {
if (!isRecord(index) || !Array.isArray(index.plugins)) {
return null;
}
const records: Record<string, PluginInstallRecord> = {};
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<Record<string, PluginInstallRecord> | null> {
const parsed = await readJsonFile<unknown>(resolveInstalledPluginIndexStorePath(options));
return extractPluginInstallRecordsFromPersistedInstalledPluginIndex(parsed);
}
export function readPersistedInstalledPluginIndexInstallRecordsSync(
options: InstalledPluginIndexStoreOptions = {},
): Record<string, PluginInstallRecord> | null {
const parsed = readJsonFileSync(resolveInstalledPluginIndexStorePath(options));
return extractPluginInstallRecordsFromPersistedInstalledPluginIndex(parsed);
}
export async function loadInstalledPluginIndexInstallRecords(
params: InstalledPluginIndexStoreOptions = {},
): Promise<Record<string, PluginInstallRecord>> {
return cloneInstallRecords((await readPersistedInstalledPluginIndexInstallRecords(params)) ?? {});
}
export function loadInstalledPluginIndexInstallRecordsSync(
params: InstalledPluginIndexStoreOptions = {},
): Record<string, PluginInstallRecord> {
return cloneInstallRecords(readPersistedInstalledPluginIndexInstallRecordsSync(params) ?? {});
}

View File

@@ -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<ReturnType<typeof readPersistedInstalledPluginIndex>>,
): Record<string, PluginInstallRecord> | null {
if (!index) {
return null;
}
return extractPluginInstallRecordsFromInstalledPluginIndex(index);
}
function cloneInstallRecords(
records: Record<string, PluginInstallRecord> | undefined,
): Record<string, PluginInstallRecord> {
return structuredClone(records ?? {});
}
export function resolveInstalledPluginIndexRecordsStorePath(
options: InstalledPluginIndexRecordStoreOptions = {},
): string {
return resolveInstalledPluginIndexStorePath(options);
}
export async function readPersistedInstalledPluginIndexInstallRecords(
options: InstalledPluginIndexRecordStoreOptions = {},
): Promise<Record<string, PluginInstallRecord> | null> {
return toInstallRecords(await readPersistedInstalledPluginIndex(options));
}
export function readPersistedInstalledPluginIndexInstallRecordsSync(
options: InstalledPluginIndexRecordStoreOptions = {},
): Record<string, PluginInstallRecord> | null {
return toInstallRecords(readPersistedInstalledPluginIndexSync(options));
}
export async function writePersistedInstalledPluginIndexInstallRecords(
records: Record<string, PluginInstallRecord>,
options: InstalledPluginIndexRecordRefreshOptions = {},
@@ -70,18 +49,6 @@ export async function writePersistedInstalledPluginIndexInstallRecords(
return resolveInstalledPluginIndexRecordsStorePath(options);
}
export async function loadInstalledPluginIndexInstallRecords(
params: InstalledPluginIndexRecordStoreOptions = {},
): Promise<Record<string, PluginInstallRecord>> {
return cloneInstallRecords((await readPersistedInstalledPluginIndexInstallRecords(params)) ?? {});
}
export function loadInstalledPluginIndexInstallRecordsSync(
params: InstalledPluginIndexRecordStoreOptions = {},
): Record<string, PluginInstallRecord> {
return cloneInstallRecords(readPersistedInstalledPluginIndexInstallRecordsSync(params) ?? {});
}
export function withPluginInstallRecords(
config: OpenClawConfig,
records: Record<string, PluginInstallRecord>,

View File

@@ -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);
}

View File

@@ -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<InstalledPluginIndex | null> {

View File

@@ -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,