import fs from "node:fs/promises"; import JSON5 from "json5"; export type Bm25Language = "zh" | "en"; export interface MigrationPluginConfigTarget { url: string; username: string; apiKey: string; database: string; alias: string; embeddingModel: string; timeout: number; bm25Enabled: boolean; bm25Language: Bm25Language; } export interface WriteMigrationPluginConfigParams { configPath: string; pluginId: string; tcvdb: { url: string; username: string; apiKey: string; database: string; alias: string; embeddingModel: string; timeout: number; }; bm25: { enabled: boolean; language: Bm25Language; }; } interface ConfigWriteAdapterDeps { fs?: Pick; parseConfig?: (raw: string) => unknown; } function asRecord(value: unknown): Record { return value !== null && typeof value === "object" && !Array.isArray(value) ? { ...(value as Record) } : {}; } export function buildMigrationPluginConfigPatch(target: MigrationPluginConfigTarget): Record { return { storeBackend: "tcvdb", tcvdb: { url: target.url, username: target.username, apiKey: target.apiKey, database: target.database, alias: target.alias, embeddingModel: target.embeddingModel, timeout: target.timeout, }, bm25: { enabled: target.bm25Enabled, language: target.bm25Language, }, }; } function applyPluginConfigPatch( sourceConfig: Record, pluginId: string, patch: Record, ): Record { const nextConfig = asRecord(sourceConfig); const plugins = asRecord(nextConfig.plugins); const entries = asRecord(plugins.entries); const targetEntry = asRecord(entries[pluginId]); const targetPluginConfig = asRecord(targetEntry.config); const patchTcvdb = asRecord(patch.tcvdb); const patchBm25 = asRecord(patch.bm25); const mergedPluginConfig: Record = { ...targetPluginConfig, ...patch, tcvdb: { ...asRecord(targetPluginConfig.tcvdb), ...patchTcvdb, }, bm25: { ...asRecord(targetPluginConfig.bm25), ...patchBm25, }, }; entries[pluginId] = { ...targetEntry, config: mergedPluginConfig, }; plugins.entries = entries; nextConfig.plugins = plugins; return nextConfig; } export async function writeMigrationPluginConfig( params: WriteMigrationPluginConfigParams, deps: ConfigWriteAdapterDeps = {}, ): Promise { const fsImpl = deps.fs ?? fs; const parseConfig = deps.parseConfig ?? ((raw: string) => JSON5.parse(raw)); let parsed: unknown; try { parsed = parseConfig(await fsImpl.readFile(params.configPath, "utf-8")); } catch { throw new Error( `Config migration writer only supports single-file JSON/JSON5: ${params.configPath}`, ); } const nextConfig = applyPluginConfigPatch( asRecord(parsed), params.pluginId, buildMigrationPluginConfigPatch({ ...params.tcvdb, bm25Enabled: params.bm25.enabled, bm25Language: params.bm25.language, }), ); await fsImpl.writeFile(params.configPath, `${JSON.stringify(nextConfig, null, 2)}\n`, "utf-8"); }