import { readFile, writeFile, mkdir, rename, unlink } from 'node:fs/promises'; import { existsSync } from 'node:fs'; import path from 'node:path'; import { logger } from '@/lib/logger'; function getAdminDir(): string { return process.env.ADMIN_DATA_DIR || path.join(process.cwd(), 'data', 'admin'); } function getPluginConfigDir(): string { return path.join(getAdminDir(), 'plugin-config'); } function configPath(pluginId: string): string { return path.join(getPluginConfigDir(), `${pluginId}.json`); } async function ensureDir(dir: string): Promise { if (!existsSync(dir)) { await mkdir(dir, { recursive: true }); } } /** * Get all config for a plugin. */ export async function getPluginConfig(pluginId: string): Promise> { try { const raw = await readFile(configPath(pluginId), 'utf-8'); return JSON.parse(raw); } catch (error) { if ((error as NodeJS.ErrnoException).code === 'ENOENT') return {}; logger.warn(`Failed to read plugin config for ${pluginId}`, { error: error instanceof Error ? error.message : 'Unknown error', }); return {}; } } /** * Set a single config key for a plugin. */ export async function setPluginConfig(pluginId: string, key: string, value: unknown): Promise { const dir = getPluginConfigDir(); await ensureDir(dir); const config = await getPluginConfig(pluginId); config[key] = value; const filePath = configPath(pluginId); const tmpPath = filePath + '.tmp'; await writeFile(tmpPath, JSON.stringify(config, null, 2), 'utf-8'); await rename(tmpPath, filePath); } /** * Delete a single config key for a plugin. */ export async function deletePluginConfigKey(pluginId: string, key: string): Promise { const config = await getPluginConfig(pluginId); delete config[key]; if (Object.keys(config).length === 0) { try { await unlink(configPath(pluginId)); } catch { /* ok if missing */ } return; } const dir = getPluginConfigDir(); await ensureDir(dir); const filePath = configPath(pluginId); const tmpPath = filePath + '.tmp'; await writeFile(tmpPath, JSON.stringify(config, null, 2), 'utf-8'); await rename(tmpPath, filePath); } /** * Delete all config for a plugin (used when uninstalling). */ export async function deleteAllPluginConfig(pluginId: string): Promise { try { await unlink(configPath(pluginId)); } catch { /* ok if missing */ } }