UNPKG

@deepbrainspace/nx-surrealdb

Version:

NX plugin for SurrealDB migrations with modular architecture

230 lines 8.93 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ConfigLoader = void 0; const tslib_1 = require("tslib"); const fs = tslib_1.__importStar(require("fs/promises")); const path = tslib_1.__importStar(require("path")); class ConfigLoader { static async loadConfig(basePath, configPath) { const configFile = configPath || (await this.findConfigFile(basePath)); if (!configFile) { return null; } const fullPath = path.isAbsolute(configFile) ? configFile : path.join(basePath, configFile); try { const content = await fs.readFile(fullPath, 'utf-8'); const config = this.parseConfig(content, fullPath); this.validateConfig(config); return config; } catch (error) { throw new Error(`Failed to load configuration from ${fullPath}: ${error.message}`); } } static async findConfigFile(basePath) { for (const filename of this.DEFAULT_CONFIG_FILES) { const fullPath = path.join(basePath, filename); try { await fs.access(fullPath); return filename; } catch { // File doesn't exist, continue } } return null; } static parseConfig(content, filePath) { const ext = path.extname(filePath).toLowerCase(); if (ext === '.json') { return JSON.parse(content); } else if (ext === '.yaml' || ext === '.yml') { // Basic YAML parsing - for full YAML support, we'd need js-yaml // For now, support simple key-value YAML that can be converted to JSON return this.parseSimpleYaml(content); } else { throw new Error(`Unsupported config file format: ${ext}. Supported formats: .json, .yaml, .yml`); } } static parseSimpleYaml(content) { // This is a minimal YAML parser for basic structures // In production, we'd use a proper YAML library like js-yaml try { // Remove comments and normalize content .split('\n') .map(line => line.trim()) .filter(line => line && !line.startsWith('#')); // For now, just try to parse as JSON if it looks JSON-like if (content.trim().startsWith('{')) { return JSON.parse(content); } // Otherwise, throw an error suggesting JSON format throw new Error('YAML parsing not fully implemented. Please use JSON format.'); } catch (error) { throw new Error(`Invalid YAML format: ${error.message}`); } } static validateConfig(config) { const errors = []; if (!config || typeof config !== 'object') { throw new Error('Configuration must be an object'); } if (!config.modules || typeof config.modules !== 'object') { errors.push({ field: 'modules', message: 'modules field is required and must be an object', value: config.modules, }); } else { // Validate each module for (const [moduleId, moduleConfig] of Object.entries(config.modules)) { this.validateModule(moduleId, moduleConfig, errors); } // Validate dependencies exist this.validateDependencies(config.modules, errors); } if (config.settings && typeof config.settings !== 'object') { errors.push({ field: 'settings', message: 'settings must be an object', value: config.settings, }); } if (errors.length > 0) { const errorMessages = errors.map(e => `${e.field}: ${e.message}`).join('; '); throw new Error(`Configuration validation failed: ${errorMessages}`); } } static validateModule(moduleId, moduleConfig, errors) { const fieldPrefix = `modules.${moduleId}`; // Validate module ID format if (!this.MODULE_PATTERN.test(moduleId)) { errors.push({ field: fieldPrefix, message: 'Module ID must follow pattern: XXX_name (e.g., 010_auth)', value: moduleId, }); } if (!moduleConfig || typeof moduleConfig !== 'object') { errors.push({ field: fieldPrefix, message: 'Module configuration must be an object', value: moduleConfig, }); return; } if (!moduleConfig.name || typeof moduleConfig.name !== 'string') { errors.push({ field: `${fieldPrefix}.name`, message: 'Module name is required and must be a string', value: moduleConfig.name, }); } if (!Array.isArray(moduleConfig.dependencies)) { errors.push({ field: `${fieldPrefix}.dependencies`, message: 'Module dependencies must be an array', value: moduleConfig.dependencies, }); } else { // Validate each dependency is a string moduleConfig.dependencies.forEach((dep, index) => { if (typeof dep !== 'string') { errors.push({ field: `${fieldPrefix}.dependencies[${index}]`, message: 'Dependency must be a string', value: dep, }); } }); } } static validateDependencies(modules, errors) { const moduleIds = Object.keys(modules); for (const [moduleId, moduleConfig] of Object.entries(modules)) { if (!Array.isArray(moduleConfig.dependencies)) continue; for (const dependency of moduleConfig.dependencies) { if (!moduleIds.includes(dependency)) { errors.push({ field: `modules.${moduleId}.dependencies`, message: `Dependency '${dependency}' does not exist in modules`, value: dependency, }); } } } // Check for circular dependencies this.detectCircularDependencies(modules, errors); } static detectCircularDependencies(modules, errors) { const visited = new Set(); const recursionStack = new Set(); const hasCycle = (moduleId, path) => { if (recursionStack.has(moduleId)) { const cycle = [...path, moduleId].slice(path.indexOf(moduleId)); errors.push({ field: 'modules', message: `Circular dependency detected: ${cycle.join(' → ')}`, value: cycle, }); return true; } if (visited.has(moduleId)) { return false; } visited.add(moduleId); recursionStack.add(moduleId); const moduleConfig = modules[moduleId]; if (moduleConfig && Array.isArray(moduleConfig.dependencies)) { for (const dependency of moduleConfig.dependencies) { if (hasCycle(dependency, [...path, moduleId])) { return true; } } } recursionStack.delete(moduleId); return false; }; for (const moduleId of Object.keys(modules)) { if (!visited.has(moduleId)) { hasCycle(moduleId, []); } } } static createDefaultConfig(modules) { const config = { modules: {}, settings: { configFormat: 'json', useTransactions: true, }, }; // Sort modules to establish basic dependency order const sortedModules = modules.sort(); for (let i = 0; i < sortedModules.length; i++) { const moduleId = sortedModules[i]; const match = moduleId.match(this.MODULE_PATTERN); const name = match ? match[2] : moduleId; config.modules[moduleId] = { name: name.charAt(0).toUpperCase() + name.slice(1).replace(/_/g, ' '), description: `${name} module`, dependencies: i > 0 ? [sortedModules[i - 1]] : [], }; } return config; } } exports.ConfigLoader = ConfigLoader; ConfigLoader.DEFAULT_CONFIG_FILES = ['config.json', 'config.yaml', 'config.yml']; ConfigLoader.MODULE_PATTERN = /^(\d{1,4})_(.+)$/; //# sourceMappingURL=config-loader.js.map