import { Env } from './Env'; /** * Configuration template with placeholder support */ export class ConfigTemplate { public static readonly Empty: ConfigTemplate = new ConfigTemplate(); private _contentText: string; private _params: string[]; constructor(contentText: string = '') { this._contentText = contentText; this._params = this.extractParams(); } public get content(): string { return this._contentText; } /** * Backward compatibility getter */ public get Params(): string[] { return this.params; } public get params(): string[] { return [...this._params]; } /** * Extracts @placeholder@ patterns from template content */ private extractParams(): string[] { const regex = /@(\w+)@/g; const paramsSet = new Set(); let match; while ((match = regex.exec(this._contentText)) !== null) { paramsSet.add(match[1]); } return Array.from(paramsSet); } } /** * Main configuration container */ export class Config { public static readonly ENV_NAME_PARAM = 'env_name'; public envs: Env[] = []; public template: ConfigTemplate = ConfigTemplate.Empty; /** * Sets environments (backward compatibility) */ public addEnvs(envs: Env[]): void { this.envs = envs; } /** * Sets environments */ public setEnvs(envs: Env[]): void { this.envs = envs; } /** * Sets template content (backward compatibility) */ public addTemplate(content: string): void { this.setTemplate(content); } /** * Sets template content */ public setTemplate(content: string): void { this.template = new ConfigTemplate(content); } /** * Gets template as JSON string */ public getTemplateAsJson(): string { try { return this.template.content; } catch { return '{}'; } } /** * Updates template by adding placeholders for environment params */ public updateTemplateFromEnv(env: Env): void { // If template is empty, initialize with empty object if (!this.template.content || !this.template.content.trim()) { this.template = new ConfigTemplate('{}'); } // Try to parse existing template let templateObj: Record = {}; let hasExistingContent = false; try { if (this.template.content.trim()) { templateObj = JSON.parse(this.template.content); hasExistingContent = Object.keys(templateObj).length > 0; } } catch { // If invalid JSON, preserve the raw content and don't modify return; } // Add placeholders for params that don't exist yet let hasChanges = false; for (const param of env.params) { if (param.name && param.name.trim()) { const paramName = param.name.trim(); const placeholder = `@${paramName}@`; const templateKey = `!!! ${paramName}`; // Check if placeholder exists anywhere in template if (!this.template.content.includes(placeholder)) { // Only add if not already in templateObj if (!templateObj[templateKey]) { templateObj[templateKey] = placeholder; hasChanges = true; } } } } // Only update if there are actual changes if (hasChanges || !hasExistingContent) { this.template = new ConfigTemplate(JSON.stringify(templateObj, null, 4)); } } /** * Validates that all template placeholders have corresponding params (backward compatibility) */ public validateParams(): string[] { return this.validatePlaceholders(); } /** * Validates that all template placeholders have corresponding params */ public validatePlaceholders(): string[] { const defaultEnv = this.envs.find(e => e.name === 'DEFAULT'); const customEnvs = this.envs.filter(e => e.name !== 'DEFAULT'); // Collect param names from DEFAULT const defaultParamNames = new Set( defaultEnv?.getParamNames() || [] ); // Collect param names from all custom envs const customParamNames = new Set( customEnvs.flatMap(e => e.getParamNames()) ); // Find missing placeholders const missingParams: string[] = []; for (const placeholder of this.template.params) { if (placeholder === Config.ENV_NAME_PARAM) continue; const inDefault = defaultParamNames.has(placeholder); const inCustom = customParamNames.has(placeholder); if (!inDefault && !inCustom) { missingParams.push(placeholder); } } return missingParams; } /** * Creates a deep copy of the config */ public clone(): Config { const cloned = new Config(); cloned.envs = [...this.envs]; cloned.template = this.template; return cloned; } /** * Checks if config is empty (no environments or only DEFAULT with no params and empty template) */ public isEmpty(): boolean { if (this.envs.length === 0) { return true; } // Check if only DEFAULT exists with no params if (this.envs.length === 1 && this.envs[0].name === 'DEFAULT') { const hasParams = this.envs[0].params.length > 0; const hasTemplate = this.template.content.trim() && this.template.content !== '{}'; return !hasParams && !hasTemplate; } return false; } }