import { Config } from '../utils/types.js';
import { DEFAULT_CONFIG } from './defaults.js';
import fs from 'fs-extra';
import path from 'path';
export class ConfigManager {
private config: Config;
private configPath?: string;
constructor(configPath?: string) {
this.configPath = configPath;
this.config = { ...DEFAULT_CONFIG };
if (configPath) {
this.loadFromFile(configPath);
}
}
private loadFromFile(configPath: string): void {
try {
const fileContent = fs.readFileSync(configPath, 'utf-8');
const userConfig = JSON.parse(fileContent);
this.config = this.mergeConfig(DEFAULT_CONFIG, userConfig);
} catch (error) {
console.error(`Failed to load config from ${configPath}, using defaults`, error);
}
}
private mergeConfig(defaults: Config, user: Partial<Config>): Config {
return {
...defaults,
...user,
security: { ...defaults.security, ...user.security },
features: { ...defaults.features, ...user.features },
logging: { ...defaults.logging, ...user.logging },
};
}
getConfig(): Config {
return this.config;
}
validate(): void {
const { security, features } = this.config;
if (security.allowedVaultPaths.length === 0) {
throw new Error('At least one allowed vault path must be configured');
}
if (security.allowedPortRange[0] >= security.allowedPortRange[1]) {
throw new Error('Invalid port range configuration');
}
if (features.maxFileSizeMb <= 0) {
throw new Error('maxFileSizeMb must be positive');
}
}
/**
* Save config back to file (if configPath was provided)
* Returns true if saved, false if no configPath
*/
async saveConfig(): Promise<boolean> {
if (!this.configPath) {
return false; // Can't save if no path provided
}
try {
// Write updated config with pretty formatting
await fs.writeFile(
this.configPath,
JSON.stringify(this.config, null, 2) + '\n',
'utf-8'
);
return true;
} catch (error) {
console.error(`Failed to save config to ${this.configPath}:`, error);
return false;
}
}
/**
* Add a path to allowedVaultPaths if not already present
* Returns true if added, false if already exists
*/
addAllowedVaultPath(newPath: string): boolean {
const resolved = path.resolve(newPath);
const existing = this.config.security.allowedVaultPaths.map((p) =>
path.resolve(p)
);
if (existing.includes(resolved)) {
return false; // Already exists
}
this.config.security.allowedVaultPaths.push(newPath);
return true;
}
/**
* Add a named vault to the config
*/
addNamedVault(name: string, vaultPath: string): void {
if (!this.config.vaults) {
this.config.vaults = {};
}
this.config.vaults[name] = vaultPath;
}
}