ai-seo
Version:
AI-native JSON-LD schema utility with automated URL-to-Schema generation, intelligent content analysis, caching, rate limiting, performance monitoring, and AI optimization (ChatGPT, Voice). Complete automation & scale features. Zero runtime dependencies.
372 lines (327 loc) • 9.4 kB
JavaScript
/**
* Config Manager - v1.12.0
* Manage configuration files and formats
*
* Features:
* - Load/save configuration files
* - Multiple output formats (JSON, CSV, HTML)
* - Config validation
* - Format conversion
*/
import { existsSync, readFileSync, writeFileSync } from 'fs';
import { join } from 'path';
export class ConfigManager {
constructor(options = {}) {
this.options = {
configFile: options.configFile || '.aiseorc.json',
searchPaths: options.searchPaths || [process.cwd(), process.env.HOME || process.env.USERPROFILE]
};
this.config = this._getDefaultConfig();
}
/**
* Get default configuration
* @private
*/
_getDefaultConfig() {
return {
cache: {
enabled: true,
ttl: 3600000,
storage: 'memory'
},
rateLimiting: {
enabled: true,
maxRequests: 10,
windowMs: 60000
},
performance: {
enabled: true,
trackMemory: true,
warnThreshold: 5000
},
generation: {
concurrency: 3,
retryOnFail: true,
maxRetries: 3,
useCache: true,
optimizeFor: []
},
output: {
format: 'json',
pretty: true
}
};
}
/**
* Load configuration from file
* @param {string} path - Config file path (optional)
* @returns {boolean} Success status
*/
load(path = null) {
const configPath = path || this._findConfigFile();
if (!configPath) {
return false;
}
try {
const content = readFileSync(configPath, 'utf-8');
const loaded = JSON.parse(content);
// Merge with defaults
this.config = this._mergeConfig(this._getDefaultConfig(), loaded);
return true;
} catch (error) {
return false;
}
}
/**
* Save configuration to file
* @param {string} path - Config file path (optional)
* @returns {boolean} Success status
*/
save(path = null) {
const configPath = path || join(process.cwd(), this.options.configFile);
try {
const content = JSON.stringify(this.config, null, 2);
writeFileSync(configPath, content, 'utf-8');
return true;
} catch (error) {
return false;
}
}
/**
* Find configuration file
* @private
*/
_findConfigFile() {
for (const searchPath of this.options.searchPaths) {
const configPath = join(searchPath, this.options.configFile);
if (existsSync(configPath)) {
return configPath;
}
}
return null;
}
/**
* Merge configurations
* @private
*/
_mergeConfig(defaults, custom) {
const merged = { ...defaults };
for (const key in custom) {
if (typeof custom[key] === 'object' && !Array.isArray(custom[key])) {
merged[key] = { ...defaults[key], ...custom[key] };
} else {
merged[key] = custom[key];
}
}
return merged;
}
/**
* Get configuration value
* @param {string} key - Config key (supports dot notation)
* @returns {*} Config value
*/
get(key) {
const parts = key.split('.');
let value = this.config;
for (const part of parts) {
if (value && typeof value === 'object') {
value = value[part];
} else {
return undefined;
}
}
return value;
}
/**
* Set configuration value
* @param {string} key - Config key (supports dot notation)
* @param {*} value - Value to set
*/
set(key, value) {
const parts = key.split('.');
let current = this.config;
for (let i = 0; i < parts.length - 1; i++) {
if (!current[parts[i]]) {
current[parts[i]] = {};
}
current = current[parts[i]];
}
current[parts[parts.length - 1]] = value;
}
/**
* Validate configuration
* @returns {Object} Validation result
*/
validate() {
const errors = [];
// Validate cache settings
if (this.config.cache) {
if (typeof this.config.cache.ttl !== 'number' || this.config.cache.ttl < 0) {
errors.push('cache.ttl must be a positive number');
}
if (!['memory', 'file'].includes(this.config.cache.storage)) {
errors.push('cache.storage must be "memory" or "file"');
}
}
// Validate rate limiting
if (this.config.rateLimiting) {
if (typeof this.config.rateLimiting.maxRequests !== 'number' || this.config.rateLimiting.maxRequests < 1) {
errors.push('rateLimiting.maxRequests must be a positive number');
}
}
// Validate generation settings
if (this.config.generation) {
if (typeof this.config.generation.concurrency !== 'number' || this.config.generation.concurrency < 1) {
errors.push('generation.concurrency must be a positive number');
}
}
return {
valid: errors.length === 0,
errors
};
}
/**
* Export configuration
* @returns {Object} Configuration
*/
export() {
return JSON.parse(JSON.stringify(this.config));
}
/**
* Reset to defaults
*/
reset() {
this.config = this._getDefaultConfig();
}
}
/**
* Format result to different output formats
*/
export class OutputFormatter {
/**
* Format result to JSON
* @param {*} data - Data to format
* @param {boolean} pretty - Pretty print
* @returns {string} Formatted JSON
*/
static toJSON(data, pretty = true) {
return JSON.stringify(data, null, pretty ? 2 : 0);
}
/**
* Format result to CSV
* @param {Array|Object} data - Data to format
* @returns {string} Formatted CSV
*/
static toCSV(data) {
// Handle array of objects
if (Array.isArray(data)) {
if (data.length === 0) {
return '';
}
const headers = Object.keys(data[0]);
const rows = [headers.join(',')];
for (const item of data) {
const values = headers.map(h => {
const value = item[h];
if (typeof value === 'string' && value.includes(',')) {
return `"${value}"`;
}
return value;
});
rows.push(values.join(','));
}
return rows.join('\n');
}
// Handle single object
const rows = ['key,value'];
for (const [key, value] of Object.entries(data)) {
rows.push(`${key},${value}`);
}
return rows.join('\n');
}
/**
* Format result to HTML
* @param {*} data - Data to format
* @returns {string} Formatted HTML
*/
static toHTML(data) {
let html = '<!DOCTYPE html>\n<html>\n<head>\n';
html += '<meta charset="UTF-8">\n';
html += '<title>AI-SEO Results</title>\n';
html += '<style>\n';
html += 'body { font-family: Arial, sans-serif; margin: 20px; }\n';
html += 'table { border-collapse: collapse; width: 100%; }\n';
html += 'th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }\n';
html += 'th { background-color: #4CAF50; color: white; }\n';
html += 'pre { background: #f4f4f4; padding: 10px; overflow: auto; }\n';
html += '</style>\n</head>\n<body>\n';
html += '<h1>AI-SEO Results</h1>\n';
if (Array.isArray(data)) {
html += '<table>\n';
if (data.length > 0) {
const headers = Object.keys(data[0]);
html += '<tr>';
for (const header of headers) {
html += `<th>${header}</th>`;
}
html += '</tr>\n';
for (const item of data) {
html += '<tr>';
for (const header of headers) {
const value = item[header];
if (typeof value === 'object') {
html += `<td><pre>${JSON.stringify(value, null, 2)}</pre></td>`;
} else {
html += `<td>${value}</td>`;
}
}
html += '</tr>\n';
}
}
html += '</table>\n';
} else {
html += '<pre>' + JSON.stringify(data, null, 2) + '</pre>\n';
}
html += '</body>\n</html>';
return html;
}
/**
* Auto-detect and format
* @param {*} data - Data to format
* @param {string} format - Output format
* @param {Object} options - Format options
* @returns {string} Formatted output
*/
static format(data, format = 'json', options = {}) {
switch (format.toLowerCase()) {
case 'json':
return this.toJSON(data, options.pretty !== false);
case 'csv':
return this.toCSV(data);
case 'html':
return this.toHTML(data);
default:
return this.toJSON(data);
}
}
}
// Global config instance
let globalConfig = null;
/**
* Get global config manager
* @param {Object} options - Config options
* @returns {ConfigManager} Config manager
*/
export function getConfigManager(options = {}) {
if (!globalConfig) {
globalConfig = new ConfigManager(options);
globalConfig.load(); // Auto-load if file exists
}
return globalConfig;
}
/**
* Reset global config
*/
export function resetConfigManager() {
globalConfig = null;
}