@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
JavaScript
"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;
}