UNPKG

@re-shell/cli

Version:

Full-stack development platform uniting microservices and microfrontends. Build complete applications with .NET (ASP.NET Core Web API, Minimal API), Java (Spring Boot, Quarkus, Micronaut, Vert.x), Rust (Actix-Web, Warp, Rocket, Axum), Python (FastAPI, Dja

357 lines (356 loc) 12.3 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.ConfigManager = void 0; exports.createConfigManager = createConfigManager; exports.getGlobalConfig = getGlobalConfig; exports.setGlobalConfig = setGlobalConfig; const fs = __importStar(require("fs-extra")); const path = __importStar(require("path")); const yaml = __importStar(require("yaml")); const events_1 = require("events"); const error_handler_1 = require("../utils/error-handler"); class ConfigManager extends events_1.EventEmitter { constructor(options = {}) { super(); this.config = {}; this.projectConfig = {}; this.envConfig = {}; this.runtimeConfig = {}; this.watchers = new Map(); this.globalPath = options.globalPath ?? path.join(process.env.HOME || process.env.USERPROFILE || '.', '.re-shell', 'config.yaml'); this.projectPath = options.projectPath; this.schema = options.schema; this.defaults = options.defaults ?? {}; this.envPrefix = options.envPrefix ?? 'RESHELL_'; this.autoSave = options.autoSave ?? true; this.watchEnabled = options.watch ?? false; this.load(); } load() { // Load defaults this.config = { ...this.defaults }; // Load global config if (fs.existsSync(this.globalPath)) { try { const content = fs.readFileSync(this.globalPath, 'utf8'); const globalConfig = yaml.parse(content) || {}; this.mergeConfig(this.config, globalConfig); if (this.watchEnabled) { this.watchFile(this.globalPath, 'global'); } } catch (error) { this.emit('error', new Error(`Failed to load global config: ${error.message}`)); } } // Load project config if (this.projectPath && fs.existsSync(this.projectPath)) { try { const content = fs.readFileSync(this.projectPath, 'utf8'); this.projectConfig = yaml.parse(content) || {}; this.mergeConfig(this.config, this.projectConfig); if (this.watchEnabled) { this.watchFile(this.projectPath, 'project'); } } catch (error) { this.emit('error', new Error(`Failed to load project config: ${error.message}`)); } } // Load environment variables this.loadEnvConfig(); this.mergeConfig(this.config, this.envConfig); // Apply runtime config last (highest priority) this.mergeConfig(this.config, this.runtimeConfig); // Validate config if (this.schema) { this.validateConfig(); } this.emit('loaded', this.config); } loadEnvConfig() { this.envConfig = {}; for (const [key, value] of Object.entries(process.env)) { if (key.startsWith(this.envPrefix)) { const configKey = key .substring(this.envPrefix.length) .toLowerCase() .replace(/_/g, '.'); this.setNestedValue(this.envConfig, configKey, this.parseEnvValue(value)); } } } parseEnvValue(value) { // Try to parse as JSON try { return JSON.parse(value); } catch { // Try to parse as number if (/^\d+$/.test(value)) { return parseInt(value, 10); } if (/^\d*\.\d+$/.test(value)) { return parseFloat(value); } // Parse as boolean if (value.toLowerCase() === 'true') return true; if (value.toLowerCase() === 'false') return false; // Return as string return value; } } mergeConfig(target, source) { for (const [key, value] of Object.entries(source)) { if (value !== null && typeof value === 'object' && !Array.isArray(value)) { if (!(key in target) || typeof target[key] !== 'object') { target[key] = {}; } this.mergeConfig(target[key], value); } else { target[key] = value; } } } validateConfig() { if (!this.schema) return; for (const [key, schema] of Object.entries(this.schema)) { const value = this.get(key); // Check required if (schema.required && value === undefined) { throw new error_handler_1.ValidationError(`Configuration key '${key}' is required`); } // Check type if (value !== undefined) { const actualType = Array.isArray(value) ? 'array' : typeof value; if (actualType !== schema.type) { throw new error_handler_1.ValidationError(`Configuration key '${key}' must be of type ${schema.type}, got ${actualType}`); } } // Run validator if (schema.validator && value !== undefined) { if (!schema.validator(value)) { throw new error_handler_1.ValidationError(`Configuration key '${key}' failed validation`); } } // Apply transformer if (schema.transformer && value !== undefined) { this.set(key, schema.transformer(value)); } } } watchFile(filepath, type) { if (this.watchers.has(filepath)) return; const watcher = fs.watch(filepath, (eventType) => { if (eventType === 'change') { this.emit('change', { type, path: filepath }); this.load(); } }); this.watchers.set(filepath, watcher); } get(key, defaultValue) { const keys = key.split('.'); let value = this.config; for (const k of keys) { if (value && typeof value === 'object' && k in value) { value = value[k]; } else { return defaultValue; } } return value; } set(key, value) { this.setNestedValue(this.runtimeConfig, key, value); this.setNestedValue(this.config, key, value); this.emit('set', { key, value }); if (this.autoSave && this.projectPath) { this.save(); } } setNestedValue(obj, key, value) { const keys = key.split('.'); let current = obj; for (let i = 0; i < keys.length - 1; i++) { const k = keys[i]; if (!(k in current) || typeof current[k] !== 'object') { current[k] = {}; } current = current[k]; } current[keys[keys.length - 1]] = value; } has(key) { return this.get(key) !== undefined; } delete(key) { const keys = key.split('.'); let current = this.config; for (let i = 0; i < keys.length - 1; i++) { const k = keys[i]; if (!(k in current) || typeof current[k] !== 'object') { return; } current = current[k]; } delete current[keys[keys.length - 1]]; // Also delete from runtime config this.deleteNestedValue(this.runtimeConfig, key); this.emit('delete', { key }); if (this.autoSave && this.projectPath) { this.save(); } } deleteNestedValue(obj, key) { const keys = key.split('.'); let current = obj; for (let i = 0; i < keys.length - 1; i++) { const k = keys[i]; if (!(k in current) || typeof current[k] !== 'object') { return; } current = current[k]; } delete current[keys[keys.length - 1]]; } getAll() { return { ...this.config }; } save(filePath) { const savePath = filePath || this.projectPath; if (!savePath) { throw new Error('No save path specified'); } try { // Ensure directory exists fs.ensureDirSync(path.dirname(savePath)); // Merge project config with runtime config const configToSave = { ...this.projectConfig }; this.mergeConfig(configToSave, this.runtimeConfig); // Write config const content = yaml.stringify(configToSave, { indent: 2, lineWidth: 0 }); fs.writeFileSync(savePath, content, 'utf8'); this.emit('saved', { path: savePath }); } catch (error) { throw new Error(`Failed to save config: ${error.message}`); } } reload() { this.load(); } reset() { this.config = { ...this.defaults }; this.projectConfig = {}; this.envConfig = {}; this.runtimeConfig = {}; this.loadEnvConfig(); this.mergeConfig(this.config, this.envConfig); this.emit('reset'); } watchFiles(enabled) { if (enabled === this.watchEnabled) return; this.watchEnabled = enabled; if (enabled) { if (fs.existsSync(this.globalPath)) { this.watchFile(this.globalPath, 'global'); } if (this.projectPath && fs.existsSync(this.projectPath)) { this.watchFile(this.projectPath, 'project'); } } else { for (const [path, watcher] of this.watchers) { watcher.close(); } this.watchers.clear(); } } dispose() { this.watchFiles(false); this.removeAllListeners(); } // Utility methods getGlobalPath() { return this.globalPath; } getProjectPath() { return this.projectPath; } setProjectPath(path) { this.projectPath = path; this.load(); } getSchema() { return this.schema; } setSchema(schema) { this.schema = schema; this.validateConfig(); } } exports.ConfigManager = ConfigManager; // Global config instance let globalConfig = null; function createConfigManager(options) { return new ConfigManager(options); } function getGlobalConfig() { if (!globalConfig) { const projectPath = path.join(process.cwd(), '.re-shell', 'config.yaml'); globalConfig = new ConfigManager({ projectPath: fs.existsSync(projectPath) ? projectPath : undefined }); } return globalConfig; } function setGlobalConfig(config) { if (globalConfig) { globalConfig.dispose(); } globalConfig = config; }