Files

1 line
63 KiB
JSON

{"repo": "chetanft/compare", "files": {"src/config/index.js": {"4069e67806b3c648a4401ba9fb2d2d3822e6e384": {"t": 1785746691.780842, "n": 7838, "h": "a5c15865c726ddfa", "c": "/**\n * Unified Configuration System\n * Single source of truth for all application configuration\n */\n\nimport { readFileSync, existsSync } from 'fs';\nimport { join, dirname } from 'path';\nimport { fileURLToPath } from 'url';\nimport { PORTS } from './PORTS.js';\nimport { detectDeploymentMode } from '../database/index.js';\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = dirname(__filename);\n\nlet cachedConfig = null;\nlet configLoadTime = 0;\nconst CONFIG_CACHE_TTL = 5 * 60 * 1000; // 5 minutes\n\n/**\n * Load configuration from environment variables and optional local config\n */\nexport async function loadConfig() {\n // Return cached config if still valid\n if (cachedConfig && Date.now() - configLoadTime < CONFIG_CACHE_TTL) {\n return cachedConfig;\n }\n\n const env = process.env;\n \n // Base configuration from environment\n const baseConfig = {\n server: {\n port: parseInt(env.PORT || env.SERVER_PORT || PORTS.SERVER.toString(), 10),\n host: env.HOST || env.SERVER_HOST || '0.0.0.0', // Bind to all interfaces for Electron compatibility\n },\n cors: {\n origins: env.CORS_ORIGINS ? env.CORS_ORIGINS.split(',') : [\n 'http://localhost:3000',\n `http://localhost:${PORTS.SERVER}`,\n `http://localhost:${PORTS.WEB_DEV}`,\n `http://localhost:${PORTS.PREVIEW}`,\n ],\n credentials: env.CORS_CREDENTIALS !== 'false',\n },\n mcp: {\n enabled: env.MCP_ENABLED !== 'false',\n url: env.MCP_URL || 'http://127.0.0.1:3845',\n endpoint: env.MCP_ENDPOINT || '/mcp',\n },\n puppeteer: {\n headless: env.PUPPETEER_HEADLESS === 'false' ? false : \n env.PUPPETEER_HEADLESS === 'true' ? true : 'new',\n timeout: parseInt(env.PUPPETEER_TIMEOUT || '30000', 10),\n protocolTimeout: parseInt(env.PUPPETEER_PROTOCOL_TIMEOUT || '300000', 10), // 5 minutes for slow sites\n executablePath: env.PUPPETEER_EXECUTABLE_PATH || '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',\n args: env.PUPPETEER_ARGS ? env.PUPPETEER_ARGS.split(',') : [\n '--no-sandbox',\n '--disable-setuid-sandbox',\n '--disable-blink-features=AutomationControlled',\n '--disable-features=VizDisplayCompositor',\n '--disable-dev-shm-usage',\n '--disable-gpu',\n '--no-first-run',\n '--disable-background-timer-throttling',\n '--disable-backgrounding-occluded-windows',\n '--disable-renderer-backgrounding',\n ],\n },\n browserPool: {\n maxConcurrentJobs: parseInt(env.BROWSER_POOL_MAX_CONCURRENT_JOBS || '3', 10),\n maxBrowsers: parseInt(env.BROWSER_POOL_MAX_BROWSERS || '5', 10),\n maxPagesPerBrowser: parseInt(env.BROWSER_POOL_MAX_PAGES || '20', 10),\n maxIdleMinutes: parseInt(env.BROWSER_POOL_MAX_IDLE_MINUTES || '10', 10),\n },\n thresholds: {\n colorDifference: parseInt(env.COLOR_DIFFERENCE_THRESHOLD || '10', 10),\n sizeDifference: parseInt(env.SIZE_DIFFERENCE_THRESHOLD || '5', 10),\n spacingDifference: parseInt(env.SPACING_DIFFERENCE_THRESHOLD || '3', 10),\n fontSizeDifference: parseInt(env.FONT_SIZE_DIFFERENCE_THRESHOLD || '2', 10),\n },\n timeouts: {\n figmaExtraction: parseInt(env.FIGMA_EXTRACTION_TIMEOUT || '60000', 10),\n webExtraction: parseInt(env.WEB_EXTRACTION_TIMEOUT || '30000', 10),\n comparison: parseInt(env.COMPARISON_TIMEOUT || '10000', 10),\n },\n security: {\n allowedHosts: env.ALLOWED_HOSTS ? env.ALLOWED_HOSTS.split(',') : [],\n rateLimit: {\n windowMs: parseInt(env.RATE_LIMIT_WINDOW_MS || String(15 * 60 * 1000), 10),\n max: parseInt(env.RATE_LIMIT_MAX || '100', 10),\n extractionMax: parseInt(env.RATE_LIMIT_EXTRACTION_MAX || '10', 10),\n },\n },\n figma: {\n apiKey: env.FIGMA_API_KEY || env.FIGMA_PERSONAL_ACCESS_TOKEN,\n },\n snapshots: {\n enabled: env.SNAPSHOTS_ENABLED !== 'false',\n maxRecords: parseInt(env.SNAPSHOT_MAX_RECORDS || '50', 10),\n includeInputs: env.SNAPSHOT_INCLUDE_INPUTS === 'true',\n },\n // Next Version Features (Production-Ready)\n nextVersion: {\n enabled: env.ENABLE_NEXT_VERSION === 'true',\n features: {\n enhancedVisualComparison: env.ENABLE_VISUAL_COMPARISON !== 'false',\n llmIntegration: env.ENABLE_LLM_INTEGRATION === 'true',\n multiPageComparison: env.ENABLE_MULTI_PAGE === 'true',\n performanceDashboard: env.ENABLE_PERFORMANCE_DASHBOARD === 'true',\n autoErrorRecovery: env.ENABLE_ERROR_RECOVERY !== 'false'\n },\n llm: {\n provider: env.LLM_PROVIDER || 'deepseek',\n apiKey: env.LLM_API_KEY,\n apiUrl: env.LLM_API_URL || 'https://api.deepseek.com/v1/chat/completions',\n model: env.LLM_MODEL || 'deepseek-chat',\n maxTokens: parseInt(env.LLM_MAX_TOKENS || '1000', 10),\n timeout: parseInt(env.LLM_TIMEOUT || '30000', 10)\n },\n authentication: {\n maxRetries: parseInt(env.AUTH_MAX_RETRIES || '3', 10),\n retryDelay: parseInt(env.AUTH_RETRY_DELAY || '2000', 10),\n selectorTimeout: parseInt(env.AUTH_SELECTOR_TIMEOUT || '15000', 10),\n skipOnFailure: env.AUTH_SKIP_ON_FAILURE !== 'false'\n },\n visualComparison: {\n diffThreshold: parseFloat(env.VISUAL_DIFF_THRESHOLD || '0.1'),\n similarity: {\n threshold: parseFloat(env.VISUAL_SIMILARITY_THRESHOLD || '85.0')\n }\n }\n },\n };\n\n // Try to load local configuration file (optional)\n const localConfigPath = join(__dirname, '../../config.local.js');\n let localConfig = {};\n \n try {\n if (existsSync(localConfigPath)) {\n const localConfigModule = await import(localConfigPath);\n localConfig = localConfigModule.default || localConfigModule;\n }\n } catch (error) {\n // Local config is optional, ignore errors\n }\n\n // Merge configurations (environment takes precedence)\n const mergedConfig = mergeDeep(baseConfig, localConfig);\n\n // Validate basic requirements\n if (isNaN(mergedConfig.server.port) || mergedConfig.server.port < 1024 || mergedConfig.server.port > 65535) {\n throw new Error(`Invalid port: ${mergedConfig.server.port}. Must be between 1024 and 65535.`);\n }\n\n cachedConfig = mergedConfig;\n configLoadTime = Date.now();\n return cachedConfig;\n}\n\n/**\n * Get Figma API key from environment or throw error\n */\nexport async function getFigmaApiKey() {\n const config = await loadConfig();\n const apiKey = config.figma.apiKey;\n \n if (!apiKey) {\n throw new Error(\n 'Figma API key not found. Please set FIGMA_API_KEY environment variable.'\n );\n }\n \n return apiKey;\n}\n\n/**\n * Deep merge two objects\n */\nfunction mergeDeep(target, source) {\n const result = { ...target };\n \n for (const key in source) {\n if (source[key] && typeof source[key] === 'object' && !Array.isArray(source[key])) {\n result[key] = mergeDeep(target[key] || {}, source[key]);\n } else if (source[key] !== undefined) {\n result[key] = source[key];\n }\n }\n \n // Add database configuration\n result.database = {\n deploymentMode: detectDeploymentMode(),\n databaseUrl: process.env.DATABASE_URL || null,\n supabaseUrl: process.env.SUPABASE_URL || process.env.VITE_SUPABASE_URL || null,\n supabaseServiceKey: process.env.SUPABASE_SERVICE_KEY || null,\n supabaseAnonKey: process.env.SUPABASE_ANON_KEY || process.env.VITE_SUPABASE_ANON_KEY || null,\n encryptionKey: process.env.CREDENTIAL_ENCRYPTION_KEY || process.env.LOCAL_CREDENTIAL_KEY || null\n };\n\n return result;\n}\n\n// Create singleton instance\nlet configPromise = null;\n\n/**\n * Get configuration synchronously (for compatibility)\n */\nexport function getConfig() {\n if (!configPromise) {\n configPromise = loadConfig();\n }\n return configPromise;\n}\n\n// Legacy compatibility - note this returns a promise now\nexport const config = getConfig();\nexport default getConfig; "}, "9b7de85e46f7847416dff6bd1b2d86c561ffc936": {"t": 1785766935.426253, "n": 7838, "h": "a5c15865c726ddfa", "c": "/**\n * Unified Configuration System\n * Single source of truth for all application configuration\n */\n\nimport { readFileSync, existsSync } from 'fs';\nimport { join, dirname } from 'path';\nimport { fileURLToPath } from 'url';\nimport { PORTS } from './PORTS.js';\nimport { detectDeploymentMode } from '../database/index.js';\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = dirname(__filename);\n\nlet cachedConfig = null;\nlet configLoadTime = 0;\nconst CONFIG_CACHE_TTL = 5 * 60 * 1000; // 5 minutes\n\n/**\n * Load configuration from environment variables and optional local config\n */\nexport async function loadConfig() {\n // Return cached config if still valid\n if (cachedConfig && Date.now() - configLoadTime < CONFIG_CACHE_TTL) {\n return cachedConfig;\n }\n\n const env = process.env;\n \n // Base configuration from environment\n const baseConfig = {\n server: {\n port: parseInt(env.PORT || env.SERVER_PORT || PORTS.SERVER.toString(), 10),\n host: env.HOST || env.SERVER_HOST || '0.0.0.0', // Bind to all interfaces for Electron compatibility\n },\n cors: {\n origins: env.CORS_ORIGINS ? env.CORS_ORIGINS.split(',') : [\n 'http://localhost:3000',\n `http://localhost:${PORTS.SERVER}`,\n `http://localhost:${PORTS.WEB_DEV}`,\n `http://localhost:${PORTS.PREVIEW}`,\n ],\n credentials: env.CORS_CREDENTIALS !== 'false',\n },\n mcp: {\n enabled: env.MCP_ENABLED !== 'false',\n url: env.MCP_URL || 'http://127.0.0.1:3845',\n endpoint: env.MCP_ENDPOINT || '/mcp',\n },\n puppeteer: {\n headless: env.PUPPETEER_HEADLESS === 'false' ? false : \n env.PUPPETEER_HEADLESS === 'true' ? true : 'new',\n timeout: parseInt(env.PUPPETEER_TIMEOUT || '30000', 10),\n protocolTimeout: parseInt(env.PUPPETEER_PROTOCOL_TIMEOUT || '300000', 10), // 5 minutes for slow sites\n executablePath: env.PUPPETEER_EXECUTABLE_PATH || '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',\n args: env.PUPPETEER_ARGS ? env.PUPPETEER_ARGS.split(',') : [\n '--no-sandbox',\n '--disable-setuid-sandbox',\n '--disable-blink-features=AutomationControlled',\n '--disable-features=VizDisplayCompositor',\n '--disable-dev-shm-usage',\n '--disable-gpu',\n '--no-first-run',\n '--disable-background-timer-throttling',\n '--disable-backgrounding-occluded-windows',\n '--disable-renderer-backgrounding',\n ],\n },\n browserPool: {\n maxConcurrentJobs: parseInt(env.BROWSER_POOL_MAX_CONCURRENT_JOBS || '3', 10),\n maxBrowsers: parseInt(env.BROWSER_POOL_MAX_BROWSERS || '5', 10),\n maxPagesPerBrowser: parseInt(env.BROWSER_POOL_MAX_PAGES || '20', 10),\n maxIdleMinutes: parseInt(env.BROWSER_POOL_MAX_IDLE_MINUTES || '10', 10),\n },\n thresholds: {\n colorDifference: parseInt(env.COLOR_DIFFERENCE_THRESHOLD || '10', 10),\n sizeDifference: parseInt(env.SIZE_DIFFERENCE_THRESHOLD || '5', 10),\n spacingDifference: parseInt(env.SPACING_DIFFERENCE_THRESHOLD || '3', 10),\n fontSizeDifference: parseInt(env.FONT_SIZE_DIFFERENCE_THRESHOLD || '2', 10),\n },\n timeouts: {\n figmaExtraction: parseInt(env.FIGMA_EXTRACTION_TIMEOUT || '60000', 10),\n webExtraction: parseInt(env.WEB_EXTRACTION_TIMEOUT || '30000', 10),\n comparison: parseInt(env.COMPARISON_TIMEOUT || '10000', 10),\n },\n security: {\n allowedHosts: env.ALLOWED_HOSTS ? env.ALLOWED_HOSTS.split(',') : [],\n rateLimit: {\n windowMs: parseInt(env.RATE_LIMIT_WINDOW_MS || String(15 * 60 * 1000), 10),\n max: parseInt(env.RATE_LIMIT_MAX || '100', 10),\n extractionMax: parseInt(env.RATE_LIMIT_EXTRACTION_MAX || '10', 10),\n },\n },\n figma: {\n apiKey: env.FIGMA_API_KEY || env.FIGMA_PERSONAL_ACCESS_TOKEN,\n },\n snapshots: {\n enabled: env.SNAPSHOTS_ENABLED !== 'false',\n maxRecords: parseInt(env.SNAPSHOT_MAX_RECORDS || '50', 10),\n includeInputs: env.SNAPSHOT_INCLUDE_INPUTS === 'true',\n },\n // Next Version Features (Production-Ready)\n nextVersion: {\n enabled: env.ENABLE_NEXT_VERSION === 'true',\n features: {\n enhancedVisualComparison: env.ENABLE_VISUAL_COMPARISON !== 'false',\n llmIntegration: env.ENABLE_LLM_INTEGRATION === 'true',\n multiPageComparison: env.ENABLE_MULTI_PAGE === 'true',\n performanceDashboard: env.ENABLE_PERFORMANCE_DASHBOARD === 'true',\n autoErrorRecovery: env.ENABLE_ERROR_RECOVERY !== 'false'\n },\n llm: {\n provider: env.LLM_PROVIDER || 'deepseek',\n apiKey: env.LLM_API_KEY,\n apiUrl: env.LLM_API_URL || 'https://api.deepseek.com/v1/chat/completions',\n model: env.LLM_MODEL || 'deepseek-chat',\n maxTokens: parseInt(env.LLM_MAX_TOKENS || '1000', 10),\n timeout: parseInt(env.LLM_TIMEOUT || '30000', 10)\n },\n authentication: {\n maxRetries: parseInt(env.AUTH_MAX_RETRIES || '3', 10),\n retryDelay: parseInt(env.AUTH_RETRY_DELAY || '2000', 10),\n selectorTimeout: parseInt(env.AUTH_SELECTOR_TIMEOUT || '15000', 10),\n skipOnFailure: env.AUTH_SKIP_ON_FAILURE !== 'false'\n },\n visualComparison: {\n diffThreshold: parseFloat(env.VISUAL_DIFF_THRESHOLD || '0.1'),\n similarity: {\n threshold: parseFloat(env.VISUAL_SIMILARITY_THRESHOLD || '85.0')\n }\n }\n },\n };\n\n // Try to load local configuration file (optional)\n const localConfigPath = join(__dirname, '../../config.local.js');\n let localConfig = {};\n \n try {\n if (existsSync(localConfigPath)) {\n const localConfigModule = await import(localConfigPath);\n localConfig = localConfigModule.default || localConfigModule;\n }\n } catch (error) {\n // Local config is optional, ignore errors\n }\n\n // Merge configurations (environment takes precedence)\n const mergedConfig = mergeDeep(baseConfig, localConfig);\n\n // Validate basic requirements\n if (isNaN(mergedConfig.server.port) || mergedConfig.server.port < 1024 || mergedConfig.server.port > 65535) {\n throw new Error(`Invalid port: ${mergedConfig.server.port}. Must be between 1024 and 65535.`);\n }\n\n cachedConfig = mergedConfig;\n configLoadTime = Date.now();\n return cachedConfig;\n}\n\n/**\n * Get Figma API key from environment or throw error\n */\nexport async function getFigmaApiKey() {\n const config = await loadConfig();\n const apiKey = config.figma.apiKey;\n \n if (!apiKey) {\n throw new Error(\n 'Figma API key not found. Please set FIGMA_API_KEY environment variable.'\n );\n }\n \n return apiKey;\n}\n\n/**\n * Deep merge two objects\n */\nfunction mergeDeep(target, source) {\n const result = { ...target };\n \n for (const key in source) {\n if (source[key] && typeof source[key] === 'object' && !Array.isArray(source[key])) {\n result[key] = mergeDeep(target[key] || {}, source[key]);\n } else if (source[key] !== undefined) {\n result[key] = source[key];\n }\n }\n \n // Add database configuration\n result.database = {\n deploymentMode: detectDeploymentMode(),\n databaseUrl: process.env.DATABASE_URL || null,\n supabaseUrl: process.env.SUPABASE_URL || process.env.VITE_SUPABASE_URL || null,\n supabaseServiceKey: process.env.SUPABASE_SERVICE_KEY || null,\n supabaseAnonKey: process.env.SUPABASE_ANON_KEY || process.env.VITE_SUPABASE_ANON_KEY || null,\n encryptionKey: process.env.CREDENTIAL_ENCRYPTION_KEY || process.env.LOCAL_CREDENTIAL_KEY || null\n };\n\n return result;\n}\n\n// Create singleton instance\nlet configPromise = null;\n\n/**\n * Get configuration synchronously (for compatibility)\n */\nexport function getConfig() {\n if (!configPromise) {\n configPromise = loadConfig();\n }\n return configPromise;\n}\n\n// Legacy compatibility - note this returns a promise now\nexport const config = getConfig();\nexport default getConfig; "}, "4a3131483d934001a11ed9d57af2a7d2206c5675": {"t": 1785766936.9488368, "n": 7273, "h": "7d7ffc136872ef97", "c": "/**\n * Unified Configuration System\n * Single source of truth for all application configuration\n */\n\nimport { readFileSync, existsSync } from 'fs';\nimport { join, dirname } from 'path';\nimport { fileURLToPath } from 'url';\nimport { PORTS } from './PORTS.js';\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = dirname(__filename);\n\nlet cachedConfig = null;\nlet configLoadTime = 0;\nconst CONFIG_CACHE_TTL = 5 * 60 * 1000; // 5 minutes\n\n/**\n * Load configuration from environment variables and optional local config\n */\nexport async function loadConfig() {\n // Return cached config if still valid\n if (cachedConfig && Date.now() - configLoadTime < CONFIG_CACHE_TTL) {\n return cachedConfig;\n }\n\n const env = process.env;\n \n // Base configuration from environment\n const baseConfig = {\n server: {\n port: parseInt(env.PORT || env.SERVER_PORT || PORTS.SERVER.toString(), 10),\n host: env.HOST || env.SERVER_HOST || '0.0.0.0', // Bind to all interfaces for Electron compatibility\n },\n cors: {\n origins: env.CORS_ORIGINS ? env.CORS_ORIGINS.split(',') : [\n 'http://localhost:3000',\n `http://localhost:${PORTS.SERVER}`,\n `http://localhost:${PORTS.WEB_DEV}`,\n `http://localhost:${PORTS.PREVIEW}`,\n ],\n credentials: env.CORS_CREDENTIALS !== 'false',\n },\n mcp: {\n enabled: env.MCP_ENABLED !== 'false',\n url: env.MCP_URL || 'http://127.0.0.1:3845',\n endpoint: env.MCP_ENDPOINT || '/mcp',\n },\n puppeteer: {\n headless: env.PUPPETEER_HEADLESS === 'false' ? false : \n env.PUPPETEER_HEADLESS === 'true' ? true : 'new',\n timeout: parseInt(env.PUPPETEER_TIMEOUT || '30000', 10),\n protocolTimeout: parseInt(env.PUPPETEER_PROTOCOL_TIMEOUT || '300000', 10), // 5 minutes for slow sites\n executablePath: env.PUPPETEER_EXECUTABLE_PATH || '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',\n args: env.PUPPETEER_ARGS ? env.PUPPETEER_ARGS.split(',') : [\n '--no-sandbox',\n '--disable-setuid-sandbox',\n '--disable-blink-features=AutomationControlled',\n '--disable-features=VizDisplayCompositor',\n '--disable-dev-shm-usage',\n '--disable-gpu',\n '--no-first-run',\n '--disable-background-timer-throttling',\n '--disable-backgrounding-occluded-windows',\n '--disable-renderer-backgrounding',\n ],\n },\n browserPool: {\n maxConcurrentJobs: parseInt(env.BROWSER_POOL_MAX_CONCURRENT_JOBS || '3', 10),\n maxBrowsers: parseInt(env.BROWSER_POOL_MAX_BROWSERS || '5', 10),\n maxPagesPerBrowser: parseInt(env.BROWSER_POOL_MAX_PAGES || '20', 10),\n maxIdleMinutes: parseInt(env.BROWSER_POOL_MAX_IDLE_MINUTES || '10', 10),\n },\n thresholds: {\n colorDifference: parseInt(env.COLOR_DIFFERENCE_THRESHOLD || '10', 10),\n sizeDifference: parseInt(env.SIZE_DIFFERENCE_THRESHOLD || '5', 10),\n spacingDifference: parseInt(env.SPACING_DIFFERENCE_THRESHOLD || '3', 10),\n fontSizeDifference: parseInt(env.FONT_SIZE_DIFFERENCE_THRESHOLD || '2', 10),\n },\n timeouts: {\n figmaExtraction: parseInt(env.FIGMA_EXTRACTION_TIMEOUT || '60000', 10),\n webExtraction: parseInt(env.WEB_EXTRACTION_TIMEOUT || '30000', 10),\n comparison: parseInt(env.COMPARISON_TIMEOUT || '10000', 10),\n },\n security: {\n allowedHosts: env.ALLOWED_HOSTS ? env.ALLOWED_HOSTS.split(',') : [],\n rateLimit: {\n windowMs: parseInt(env.RATE_LIMIT_WINDOW_MS || String(15 * 60 * 1000), 10),\n max: parseInt(env.RATE_LIMIT_MAX || '100', 10),\n extractionMax: parseInt(env.RATE_LIMIT_EXTRACTION_MAX || '10', 10),\n },\n },\n figma: {\n apiKey: env.FIGMA_API_KEY || env.FIGMA_PERSONAL_ACCESS_TOKEN,\n },\n snapshots: {\n enabled: env.SNAPSHOTS_ENABLED !== 'false',\n maxRecords: parseInt(env.SNAPSHOT_MAX_RECORDS || '50', 10),\n includeInputs: env.SNAPSHOT_INCLUDE_INPUTS === 'true',\n },\n // Next Version Features (Production-Ready)\n nextVersion: {\n enabled: env.ENABLE_NEXT_VERSION === 'true',\n features: {\n enhancedVisualComparison: env.ENABLE_VISUAL_COMPARISON !== 'false',\n llmIntegration: env.ENABLE_LLM_INTEGRATION === 'true',\n multiPageComparison: env.ENABLE_MULTI_PAGE === 'true',\n performanceDashboard: env.ENABLE_PERFORMANCE_DASHBOARD === 'true',\n autoErrorRecovery: env.ENABLE_ERROR_RECOVERY !== 'false'\n },\n llm: {\n provider: env.LLM_PROVIDER || 'deepseek',\n apiKey: env.LLM_API_KEY,\n apiUrl: env.LLM_API_URL || 'https://api.deepseek.com/v1/chat/completions',\n model: env.LLM_MODEL || 'deepseek-chat',\n maxTokens: parseInt(env.LLM_MAX_TOKENS || '1000', 10),\n timeout: parseInt(env.LLM_TIMEOUT || '30000', 10)\n },\n authentication: {\n maxRetries: parseInt(env.AUTH_MAX_RETRIES || '3', 10),\n retryDelay: parseInt(env.AUTH_RETRY_DELAY || '2000', 10),\n selectorTimeout: parseInt(env.AUTH_SELECTOR_TIMEOUT || '15000', 10),\n skipOnFailure: env.AUTH_SKIP_ON_FAILURE !== 'false'\n },\n visualComparison: {\n diffThreshold: parseFloat(env.VISUAL_DIFF_THRESHOLD || '0.1'),\n similarity: {\n threshold: parseFloat(env.VISUAL_SIMILARITY_THRESHOLD || '85.0')\n }\n }\n },\n };\n\n // Try to load local configuration file (optional)\n const localConfigPath = join(__dirname, '../../config.local.js');\n let localConfig = {};\n \n try {\n if (existsSync(localConfigPath)) {\n const localConfigModule = await import(localConfigPath);\n localConfig = localConfigModule.default || localConfigModule;\n }\n } catch (error) {\n // Local config is optional, ignore errors\n }\n\n // Merge configurations (environment takes precedence)\n const mergedConfig = mergeDeep(baseConfig, localConfig);\n\n // Validate basic requirements\n if (isNaN(mergedConfig.server.port) || mergedConfig.server.port < 1024 || mergedConfig.server.port > 65535) {\n throw new Error(`Invalid port: ${mergedConfig.server.port}. Must be between 1024 and 65535.`);\n }\n\n cachedConfig = mergedConfig;\n configLoadTime = Date.now();\n return cachedConfig;\n}\n\n/**\n * Get Figma API key from environment or throw error\n */\nexport async function getFigmaApiKey() {\n const config = await loadConfig();\n const apiKey = config.figma.apiKey;\n \n if (!apiKey) {\n throw new Error(\n 'Figma API key not found. Please set FIGMA_API_KEY environment variable.'\n );\n }\n \n return apiKey;\n}\n\n/**\n * Deep merge two objects\n */\nfunction mergeDeep(target, source) {\n const result = { ...target };\n \n for (const key in source) {\n if (source[key] && typeof source[key] === 'object' && !Array.isArray(source[key])) {\n result[key] = mergeDeep(target[key] || {}, source[key]);\n } else if (source[key] !== undefined) {\n result[key] = source[key];\n }\n }\n \n return result;\n}\n\n// Create singleton instance\nlet configPromise = null;\n\n/**\n * Get configuration synchronously (for compatibility)\n */\nexport function getConfig() {\n if (!configPromise) {\n configPromise = loadConfig();\n }\n return configPromise;\n}\n\n// Legacy compatibility - note this returns a promise now\nexport const config = getConfig();\nexport default getConfig; "}, "9f070a8d016c1e03606ae5d1befcf7554577822b": {"t": 1785766938.4376416, "n": 6736, "h": "681cf5915b6c71f1", "c": "/**\n * Unified Configuration System\n * Single source of truth for all application configuration\n */\n\nimport { readFileSync, existsSync } from 'fs';\nimport { join, dirname } from 'path';\nimport { fileURLToPath } from 'url';\nimport { PORTS } from './PORTS.js';\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = dirname(__filename);\n\nlet cachedConfig = null;\nlet configLoadTime = 0;\nconst CONFIG_CACHE_TTL = 5 * 60 * 1000; // 5 minutes\n\n/**\n * Load configuration from environment variables and optional local config\n */\nexport async function loadConfig() {\n // Return cached config if still valid\n if (cachedConfig && Date.now() - configLoadTime < CONFIG_CACHE_TTL) {\n return cachedConfig;\n }\n\n const env = process.env;\n \n // Base configuration from environment\n const baseConfig = {\n server: {\n port: parseInt(env.PORT || env.SERVER_PORT || PORTS.SERVER.toString(), 10),\n host: env.HOST || env.SERVER_HOST || '0.0.0.0', // Bind to all interfaces for Electron compatibility\n },\n cors: {\n origins: env.CORS_ORIGINS ? env.CORS_ORIGINS.split(',') : [\n 'http://localhost:3000',\n `http://localhost:${PORTS.SERVER}`,\n `http://localhost:${PORTS.WEB_DEV}`,\n `http://localhost:${PORTS.PREVIEW}`,\n ],\n credentials: env.CORS_CREDENTIALS !== 'false',\n },\n mcp: {\n enabled: env.MCP_ENABLED !== 'false',\n url: env.MCP_URL || 'http://127.0.0.1:3845',\n endpoint: env.MCP_ENDPOINT || '/mcp',\n },\n puppeteer: {\n headless: env.PUPPETEER_HEADLESS === 'false' ? false : \n env.PUPPETEER_HEADLESS === 'true' ? true : 'new',\n timeout: parseInt(env.PUPPETEER_TIMEOUT || '30000', 10),\n protocolTimeout: parseInt(env.PUPPETEER_PROTOCOL_TIMEOUT || '300000', 10), // 5 minutes for slow sites\n executablePath: env.PUPPETEER_EXECUTABLE_PATH || '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',\n args: env.PUPPETEER_ARGS ? env.PUPPETEER_ARGS.split(',') : [\n '--no-sandbox',\n '--disable-setuid-sandbox',\n '--disable-blink-features=AutomationControlled',\n '--disable-features=VizDisplayCompositor',\n '--disable-dev-shm-usage',\n '--disable-gpu',\n '--no-first-run',\n '--disable-background-timer-throttling',\n '--disable-backgrounding-occluded-windows',\n '--disable-renderer-backgrounding',\n ],\n },\n thresholds: {\n colorDifference: parseInt(env.COLOR_DIFFERENCE_THRESHOLD || '10', 10),\n sizeDifference: parseInt(env.SIZE_DIFFERENCE_THRESHOLD || '5', 10),\n spacingDifference: parseInt(env.SPACING_DIFFERENCE_THRESHOLD || '3', 10),\n fontSizeDifference: parseInt(env.FONT_SIZE_DIFFERENCE_THRESHOLD || '2', 10),\n },\n timeouts: {\n figmaExtraction: parseInt(env.FIGMA_EXTRACTION_TIMEOUT || '60000', 10),\n webExtraction: parseInt(env.WEB_EXTRACTION_TIMEOUT || '30000', 10),\n comparison: parseInt(env.COMPARISON_TIMEOUT || '10000', 10),\n },\n security: {\n allowedHosts: env.ALLOWED_HOSTS ? env.ALLOWED_HOSTS.split(',') : [],\n rateLimit: {\n windowMs: parseInt(env.RATE_LIMIT_WINDOW_MS || String(15 * 60 * 1000), 10),\n max: parseInt(env.RATE_LIMIT_MAX || '100', 10),\n extractionMax: parseInt(env.RATE_LIMIT_EXTRACTION_MAX || '10', 10),\n },\n },\n figma: {\n apiKey: env.FIGMA_API_KEY || env.FIGMA_PERSONAL_ACCESS_TOKEN,\n },\n // Next Version Features (Production-Ready)\n nextVersion: {\n enabled: env.ENABLE_NEXT_VERSION === 'true',\n features: {\n enhancedVisualComparison: env.ENABLE_VISUAL_COMPARISON !== 'false',\n llmIntegration: env.ENABLE_LLM_INTEGRATION === 'true',\n multiPageComparison: env.ENABLE_MULTI_PAGE === 'true',\n performanceDashboard: env.ENABLE_PERFORMANCE_DASHBOARD === 'true',\n autoErrorRecovery: env.ENABLE_ERROR_RECOVERY !== 'false'\n },\n llm: {\n provider: env.LLM_PROVIDER || 'deepseek',\n apiKey: env.LLM_API_KEY,\n apiUrl: env.LLM_API_URL || 'https://api.deepseek.com/v1/chat/completions',\n model: env.LLM_MODEL || 'deepseek-chat',\n maxTokens: parseInt(env.LLM_MAX_TOKENS || '1000', 10),\n timeout: parseInt(env.LLM_TIMEOUT || '30000', 10)\n },\n authentication: {\n maxRetries: parseInt(env.AUTH_MAX_RETRIES || '3', 10),\n retryDelay: parseInt(env.AUTH_RETRY_DELAY || '2000', 10),\n selectorTimeout: parseInt(env.AUTH_SELECTOR_TIMEOUT || '15000', 10),\n skipOnFailure: env.AUTH_SKIP_ON_FAILURE !== 'false'\n },\n visualComparison: {\n diffThreshold: parseFloat(env.VISUAL_DIFF_THRESHOLD || '0.1'),\n similarity: {\n threshold: parseFloat(env.VISUAL_SIMILARITY_THRESHOLD || '85.0')\n }\n }\n },\n };\n\n // Try to load local configuration file (optional)\n const localConfigPath = join(__dirname, '../../config.local.js');\n let localConfig = {};\n \n try {\n if (existsSync(localConfigPath)) {\n const localConfigModule = await import(localConfigPath);\n localConfig = localConfigModule.default || localConfigModule;\n }\n } catch (error) {\n // Local config is optional, ignore errors\n }\n\n // Merge configurations (environment takes precedence)\n const mergedConfig = mergeDeep(baseConfig, localConfig);\n\n // Validate basic requirements\n if (isNaN(mergedConfig.server.port) || mergedConfig.server.port < 1024 || mergedConfig.server.port > 65535) {\n throw new Error(`Invalid port: ${mergedConfig.server.port}. Must be between 1024 and 65535.`);\n }\n\n cachedConfig = mergedConfig;\n configLoadTime = Date.now();\n return cachedConfig;\n}\n\n/**\n * Get Figma API key from environment or throw error\n */\nexport async function getFigmaApiKey() {\n const config = await loadConfig();\n const apiKey = config.figma.apiKey;\n \n if (!apiKey) {\n throw new Error(\n 'Figma API key not found. Please set FIGMA_API_KEY environment variable.'\n );\n }\n \n return apiKey;\n}\n\n/**\n * Deep merge two objects\n */\nfunction mergeDeep(target, source) {\n const result = { ...target };\n \n for (const key in source) {\n if (source[key] && typeof source[key] === 'object' && !Array.isArray(source[key])) {\n result[key] = mergeDeep(target[key] || {}, source[key]);\n } else if (source[key] !== undefined) {\n result[key] = source[key];\n }\n }\n \n return result;\n}\n\n// Create singleton instance\nlet configPromise = null;\n\n/**\n * Get configuration synchronously (for compatibility)\n */\nexport function getConfig() {\n if (!configPromise) {\n configPromise = loadConfig();\n }\n return configPromise;\n}\n\n// Legacy compatibility - note this returns a promise now\nexport const config = getConfig();\nexport default getConfig; "}, "f520f7a91669b55269efff493b0a25a49994b24c": {"t": 1785766940.1941097, "n": 6684, "h": "91dcef1e1a22cd6e", "c": "/**\n * Unified Configuration System\n * Single source of truth for all application configuration\n */\n\nimport { readFileSync, existsSync } from 'fs';\nimport { join, dirname } from 'path';\nimport { fileURLToPath } from 'url';\nimport { PORTS } from './PORTS.js';\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = dirname(__filename);\n\nlet cachedConfig = null;\nlet configLoadTime = 0;\nconst CONFIG_CACHE_TTL = 5 * 60 * 1000; // 5 minutes\n\n/**\n * Load configuration from environment variables and optional local config\n */\nexport async function loadConfig() {\n // Return cached config if still valid\n if (cachedConfig && Date.now() - configLoadTime < CONFIG_CACHE_TTL) {\n return cachedConfig;\n }\n\n const env = process.env;\n \n // Base configuration from environment\n const baseConfig = {\n server: {\n port: parseInt(env.PORT || env.SERVER_PORT || PORTS.SERVER.toString(), 10),\n host: env.HOST || env.SERVER_HOST || 'localhost',\n },\n cors: {\n origins: env.CORS_ORIGINS ? env.CORS_ORIGINS.split(',') : [\n 'http://localhost:3000',\n `http://localhost:${PORTS.SERVER}`,\n `http://localhost:${PORTS.WEB_DEV}`,\n `http://localhost:${PORTS.PREVIEW}`,\n ],\n credentials: env.CORS_CREDENTIALS !== 'false',\n },\n mcp: {\n enabled: env.MCP_ENABLED !== 'false',\n url: env.MCP_URL || 'http://127.0.0.1:3845',\n endpoint: env.MCP_ENDPOINT || '/mcp',\n },\n puppeteer: {\n headless: env.PUPPETEER_HEADLESS === 'false' ? false : \n env.PUPPETEER_HEADLESS === 'true' ? true : 'new',\n timeout: parseInt(env.PUPPETEER_TIMEOUT || '30000', 10),\n protocolTimeout: parseInt(env.PUPPETEER_PROTOCOL_TIMEOUT || '300000', 10), // 5 minutes for slow sites\n executablePath: env.PUPPETEER_EXECUTABLE_PATH || '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',\n args: env.PUPPETEER_ARGS ? env.PUPPETEER_ARGS.split(',') : [\n '--no-sandbox',\n '--disable-setuid-sandbox',\n '--disable-blink-features=AutomationControlled',\n '--disable-features=VizDisplayCompositor',\n '--disable-dev-shm-usage',\n '--disable-gpu',\n '--no-first-run',\n '--disable-background-timer-throttling',\n '--disable-backgrounding-occluded-windows',\n '--disable-renderer-backgrounding',\n ],\n },\n thresholds: {\n colorDifference: parseInt(env.COLOR_DIFFERENCE_THRESHOLD || '10', 10),\n sizeDifference: parseInt(env.SIZE_DIFFERENCE_THRESHOLD || '5', 10),\n spacingDifference: parseInt(env.SPACING_DIFFERENCE_THRESHOLD || '3', 10),\n fontSizeDifference: parseInt(env.FONT_SIZE_DIFFERENCE_THRESHOLD || '2', 10),\n },\n timeouts: {\n figmaExtraction: parseInt(env.FIGMA_EXTRACTION_TIMEOUT || '60000', 10),\n webExtraction: parseInt(env.WEB_EXTRACTION_TIMEOUT || '30000', 10),\n comparison: parseInt(env.COMPARISON_TIMEOUT || '10000', 10),\n },\n security: {\n allowedHosts: env.ALLOWED_HOSTS ? env.ALLOWED_HOSTS.split(',') : [],\n rateLimit: {\n windowMs: parseInt(env.RATE_LIMIT_WINDOW_MS || String(15 * 60 * 1000), 10),\n max: parseInt(env.RATE_LIMIT_MAX || '100', 10),\n extractionMax: parseInt(env.RATE_LIMIT_EXTRACTION_MAX || '10', 10),\n },\n },\n figma: {\n apiKey: env.FIGMA_API_KEY || env.FIGMA_PERSONAL_ACCESS_TOKEN,\n },\n // Next Version Features (Production-Ready)\n nextVersion: {\n enabled: env.ENABLE_NEXT_VERSION === 'true',\n features: {\n enhancedVisualComparison: env.ENABLE_VISUAL_COMPARISON !== 'false',\n llmIntegration: env.ENABLE_LLM_INTEGRATION === 'true',\n multiPageComparison: env.ENABLE_MULTI_PAGE === 'true',\n performanceDashboard: env.ENABLE_PERFORMANCE_DASHBOARD === 'true',\n autoErrorRecovery: env.ENABLE_ERROR_RECOVERY !== 'false'\n },\n llm: {\n provider: env.LLM_PROVIDER || 'deepseek',\n apiKey: env.LLM_API_KEY,\n apiUrl: env.LLM_API_URL || 'https://api.deepseek.com/v1/chat/completions',\n model: env.LLM_MODEL || 'deepseek-chat',\n maxTokens: parseInt(env.LLM_MAX_TOKENS || '1000', 10),\n timeout: parseInt(env.LLM_TIMEOUT || '30000', 10)\n },\n authentication: {\n maxRetries: parseInt(env.AUTH_MAX_RETRIES || '3', 10),\n retryDelay: parseInt(env.AUTH_RETRY_DELAY || '2000', 10),\n selectorTimeout: parseInt(env.AUTH_SELECTOR_TIMEOUT || '15000', 10),\n skipOnFailure: env.AUTH_SKIP_ON_FAILURE !== 'false'\n },\n visualComparison: {\n diffThreshold: parseFloat(env.VISUAL_DIFF_THRESHOLD || '0.1'),\n similarity: {\n threshold: parseFloat(env.VISUAL_SIMILARITY_THRESHOLD || '85.0')\n }\n }\n },\n };\n\n // Try to load local configuration file (optional)\n const localConfigPath = join(__dirname, '../../config.local.js');\n let localConfig = {};\n \n try {\n if (existsSync(localConfigPath)) {\n const localConfigModule = await import(localConfigPath);\n localConfig = localConfigModule.default || localConfigModule;\n }\n } catch (error) {\n // Local config is optional, ignore errors\n }\n\n // Merge configurations (environment takes precedence)\n const mergedConfig = mergeDeep(baseConfig, localConfig);\n\n // Validate basic requirements\n if (isNaN(mergedConfig.server.port) || mergedConfig.server.port < 1024 || mergedConfig.server.port > 65535) {\n throw new Error(`Invalid port: ${mergedConfig.server.port}. Must be between 1024 and 65535.`);\n }\n\n cachedConfig = mergedConfig;\n configLoadTime = Date.now();\n return cachedConfig;\n}\n\n/**\n * Get Figma API key from environment or throw error\n */\nexport async function getFigmaApiKey() {\n const config = await loadConfig();\n const apiKey = config.figma.apiKey;\n \n if (!apiKey) {\n throw new Error(\n 'Figma API key not found. Please set FIGMA_API_KEY environment variable.'\n );\n }\n \n return apiKey;\n}\n\n/**\n * Deep merge two objects\n */\nfunction mergeDeep(target, source) {\n const result = { ...target };\n \n for (const key in source) {\n if (source[key] && typeof source[key] === 'object' && !Array.isArray(source[key])) {\n result[key] = mergeDeep(target[key] || {}, source[key]);\n } else if (source[key] !== undefined) {\n result[key] = source[key];\n }\n }\n \n return result;\n}\n\n// Create singleton instance\nlet configPromise = null;\n\n/**\n * Get configuration synchronously (for compatibility)\n */\nexport function getConfig() {\n if (!configPromise) {\n configPromise = loadConfig();\n }\n return configPromise;\n}\n\n// Legacy compatibility - note this returns a promise now\nexport const config = getConfig();\nexport default getConfig; "}, "d0a0b3f5fb10c676f36e117052e76b48d854621f": {"t": 1785766941.6171176, "n": 6596, "h": "1725f9bf218efaee", "c": "/**\n * Unified Configuration System\n * Single source of truth for all application configuration\n */\n\nimport { readFileSync, existsSync } from 'fs';\nimport { join, dirname } from 'path';\nimport { fileURLToPath } from 'url';\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = dirname(__filename);\n\nlet cachedConfig = null;\nlet configLoadTime = 0;\nconst CONFIG_CACHE_TTL = 5 * 60 * 1000; // 5 minutes\n\n/**\n * Load configuration from environment variables and optional local config\n */\nexport async function loadConfig() {\n // Return cached config if still valid\n if (cachedConfig && Date.now() - configLoadTime < CONFIG_CACHE_TTL) {\n return cachedConfig;\n }\n\n const env = process.env;\n \n // Base configuration from environment\n const baseConfig = {\n server: {\n port: parseInt(env.PORT || env.SERVER_PORT || '3847', 10),\n host: env.HOST || env.SERVER_HOST || 'localhost',\n },\n cors: {\n origins: env.CORS_ORIGINS ? env.CORS_ORIGINS.split(',') : [\n 'http://localhost:3000',\n 'http://localhost:3847',\n 'http://localhost:5173',\n 'http://localhost:5174',\n ],\n credentials: env.CORS_CREDENTIALS !== 'false',\n },\n mcp: {\n enabled: env.MCP_ENABLED !== 'false',\n url: env.MCP_URL || 'http://127.0.0.1:3845',\n endpoint: env.MCP_ENDPOINT || '/mcp',\n },\n puppeteer: {\n headless: env.PUPPETEER_HEADLESS === 'false' ? false : \n env.PUPPETEER_HEADLESS === 'true' ? true : 'new',\n timeout: parseInt(env.PUPPETEER_TIMEOUT || '30000', 10),\n protocolTimeout: parseInt(env.PUPPETEER_PROTOCOL_TIMEOUT || '300000', 10), // 5 minutes for slow sites\n executablePath: env.PUPPETEER_EXECUTABLE_PATH || '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',\n args: env.PUPPETEER_ARGS ? env.PUPPETEER_ARGS.split(',') : [\n '--no-sandbox',\n '--disable-setuid-sandbox',\n '--disable-blink-features=AutomationControlled',\n '--disable-features=VizDisplayCompositor',\n '--disable-dev-shm-usage',\n '--disable-gpu',\n '--no-first-run',\n '--disable-background-timer-throttling',\n '--disable-backgrounding-occluded-windows',\n '--disable-renderer-backgrounding',\n ],\n },\n thresholds: {\n colorDifference: parseInt(env.COLOR_DIFFERENCE_THRESHOLD || '10', 10),\n sizeDifference: parseInt(env.SIZE_DIFFERENCE_THRESHOLD || '5', 10),\n spacingDifference: parseInt(env.SPACING_DIFFERENCE_THRESHOLD || '3', 10),\n fontSizeDifference: parseInt(env.FONT_SIZE_DIFFERENCE_THRESHOLD || '2', 10),\n },\n timeouts: {\n figmaExtraction: parseInt(env.FIGMA_EXTRACTION_TIMEOUT || '60000', 10),\n webExtraction: parseInt(env.WEB_EXTRACTION_TIMEOUT || '30000', 10),\n comparison: parseInt(env.COMPARISON_TIMEOUT || '10000', 10),\n },\n security: {\n allowedHosts: env.ALLOWED_HOSTS ? env.ALLOWED_HOSTS.split(',') : [],\n rateLimit: {\n windowMs: parseInt(env.RATE_LIMIT_WINDOW_MS || String(15 * 60 * 1000), 10),\n max: parseInt(env.RATE_LIMIT_MAX || '100', 10),\n extractionMax: parseInt(env.RATE_LIMIT_EXTRACTION_MAX || '10', 10),\n },\n },\n figma: {\n apiKey: env.FIGMA_API_KEY || env.FIGMA_PERSONAL_ACCESS_TOKEN,\n },\n // Next Version Features (Production-Ready)\n nextVersion: {\n enabled: env.ENABLE_NEXT_VERSION === 'true',\n features: {\n enhancedVisualComparison: env.ENABLE_VISUAL_COMPARISON !== 'false',\n llmIntegration: env.ENABLE_LLM_INTEGRATION === 'true',\n multiPageComparison: env.ENABLE_MULTI_PAGE === 'true',\n performanceDashboard: env.ENABLE_PERFORMANCE_DASHBOARD === 'true',\n autoErrorRecovery: env.ENABLE_ERROR_RECOVERY !== 'false'\n },\n llm: {\n provider: env.LLM_PROVIDER || 'deepseek',\n apiKey: env.LLM_API_KEY,\n apiUrl: env.LLM_API_URL || 'https://api.deepseek.com/v1/chat/completions',\n model: env.LLM_MODEL || 'deepseek-chat',\n maxTokens: parseInt(env.LLM_MAX_TOKENS || '1000', 10),\n timeout: parseInt(env.LLM_TIMEOUT || '30000', 10)\n },\n authentication: {\n maxRetries: parseInt(env.AUTH_MAX_RETRIES || '3', 10),\n retryDelay: parseInt(env.AUTH_RETRY_DELAY || '2000', 10),\n selectorTimeout: parseInt(env.AUTH_SELECTOR_TIMEOUT || '15000', 10),\n skipOnFailure: env.AUTH_SKIP_ON_FAILURE !== 'false'\n },\n visualComparison: {\n diffThreshold: parseFloat(env.VISUAL_DIFF_THRESHOLD || '0.1'),\n similarity: {\n threshold: parseFloat(env.VISUAL_SIMILARITY_THRESHOLD || '85.0')\n }\n }\n },\n };\n\n // Try to load local configuration file (optional)\n const localConfigPath = join(__dirname, '../../config.local.js');\n let localConfig = {};\n \n try {\n if (existsSync(localConfigPath)) {\n const localConfigModule = await import(localConfigPath);\n localConfig = localConfigModule.default || localConfigModule;\n }\n } catch (error) {\n // Local config is optional, ignore errors\n }\n\n // Merge configurations (environment takes precedence)\n const mergedConfig = mergeDeep(baseConfig, localConfig);\n\n // Validate basic requirements\n if (isNaN(mergedConfig.server.port) || mergedConfig.server.port < 1024 || mergedConfig.server.port > 65535) {\n throw new Error(`Invalid port: ${mergedConfig.server.port}. Must be between 1024 and 65535.`);\n }\n\n cachedConfig = mergedConfig;\n configLoadTime = Date.now();\n return cachedConfig;\n}\n\n/**\n * Get Figma API key from environment or throw error\n */\nexport async function getFigmaApiKey() {\n const config = await loadConfig();\n const apiKey = config.figma.apiKey;\n \n if (!apiKey) {\n throw new Error(\n 'Figma API key not found. Please set FIGMA_API_KEY environment variable.'\n );\n }\n \n return apiKey;\n}\n\n/**\n * Deep merge two objects\n */\nfunction mergeDeep(target, source) {\n const result = { ...target };\n \n for (const key in source) {\n if (source[key] && typeof source[key] === 'object' && !Array.isArray(source[key])) {\n result[key] = mergeDeep(target[key] || {}, source[key]);\n } else if (source[key] !== undefined) {\n result[key] = source[key];\n }\n }\n \n return result;\n}\n\n// Create singleton instance\nlet configPromise = null;\n\n/**\n * Get configuration synchronously (for compatibility)\n */\nexport function getConfig() {\n if (!configPromise) {\n configPromise = loadConfig();\n }\n return configPromise;\n}\n\n// Legacy compatibility - note this returns a promise now\nexport const config = getConfig();\nexport default getConfig; "}, "afd67a84b8653d87d296b46ee5995042df5b974a": {"t": 1785766942.9815562, "n": 6598, "h": "cdf94da4cc6755a0", "c": "/**\n * Unified Configuration System\n * Single source of truth for all application configuration\n */\n\nimport { readFileSync, existsSync } from 'fs';\nimport { join, dirname } from 'path';\nimport { fileURLToPath } from 'url';\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = dirname(__filename);\n\nlet cachedConfig = null;\nlet configLoadTime = 0;\nconst CONFIG_CACHE_TTL = 5 * 60 * 1000; // 5 minutes\n\n/**\n * Load configuration from environment variables and optional local config\n */\nexport async function loadConfig() {\n // Return cached config if still valid\n if (cachedConfig && Date.now() - configLoadTime < CONFIG_CACHE_TTL) {\n return cachedConfig;\n }\n\n const env = process.env;\n \n // Base configuration from environment\n const baseConfig = {\n server: {\n port: parseInt(env.PORT || env.SERVER_PORT || '47832', 10),\n host: env.HOST || env.SERVER_HOST || 'localhost',\n },\n cors: {\n origins: env.CORS_ORIGINS ? env.CORS_ORIGINS.split(',') : [\n 'http://localhost:3000',\n 'http://localhost:47832',\n 'http://localhost:5173',\n 'http://localhost:5174',\n ],\n credentials: env.CORS_CREDENTIALS !== 'false',\n },\n mcp: {\n enabled: env.MCP_ENABLED !== 'false',\n url: env.MCP_URL || 'http://127.0.0.1:3845',\n endpoint: env.MCP_ENDPOINT || '/mcp',\n },\n puppeteer: {\n headless: env.PUPPETEER_HEADLESS === 'false' ? false : \n env.PUPPETEER_HEADLESS === 'true' ? true : 'new',\n timeout: parseInt(env.PUPPETEER_TIMEOUT || '30000', 10),\n protocolTimeout: parseInt(env.PUPPETEER_PROTOCOL_TIMEOUT || '300000', 10), // 5 minutes for slow sites\n executablePath: env.PUPPETEER_EXECUTABLE_PATH || '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',\n args: env.PUPPETEER_ARGS ? env.PUPPETEER_ARGS.split(',') : [\n '--no-sandbox',\n '--disable-setuid-sandbox',\n '--disable-blink-features=AutomationControlled',\n '--disable-features=VizDisplayCompositor',\n '--disable-dev-shm-usage',\n '--disable-gpu',\n '--no-first-run',\n '--disable-background-timer-throttling',\n '--disable-backgrounding-occluded-windows',\n '--disable-renderer-backgrounding',\n ],\n },\n thresholds: {\n colorDifference: parseInt(env.COLOR_DIFFERENCE_THRESHOLD || '10', 10),\n sizeDifference: parseInt(env.SIZE_DIFFERENCE_THRESHOLD || '5', 10),\n spacingDifference: parseInt(env.SPACING_DIFFERENCE_THRESHOLD || '3', 10),\n fontSizeDifference: parseInt(env.FONT_SIZE_DIFFERENCE_THRESHOLD || '2', 10),\n },\n timeouts: {\n figmaExtraction: parseInt(env.FIGMA_EXTRACTION_TIMEOUT || '60000', 10),\n webExtraction: parseInt(env.WEB_EXTRACTION_TIMEOUT || '30000', 10),\n comparison: parseInt(env.COMPARISON_TIMEOUT || '10000', 10),\n },\n security: {\n allowedHosts: env.ALLOWED_HOSTS ? env.ALLOWED_HOSTS.split(',') : [],\n rateLimit: {\n windowMs: parseInt(env.RATE_LIMIT_WINDOW_MS || String(15 * 60 * 1000), 10),\n max: parseInt(env.RATE_LIMIT_MAX || '100', 10),\n extractionMax: parseInt(env.RATE_LIMIT_EXTRACTION_MAX || '10', 10),\n },\n },\n figma: {\n apiKey: env.FIGMA_API_KEY || env.FIGMA_PERSONAL_ACCESS_TOKEN,\n },\n // Next Version Features (Production-Ready)\n nextVersion: {\n enabled: env.ENABLE_NEXT_VERSION === 'true',\n features: {\n enhancedVisualComparison: env.ENABLE_VISUAL_COMPARISON !== 'false',\n llmIntegration: env.ENABLE_LLM_INTEGRATION === 'true',\n multiPageComparison: env.ENABLE_MULTI_PAGE === 'true',\n performanceDashboard: env.ENABLE_PERFORMANCE_DASHBOARD === 'true',\n autoErrorRecovery: env.ENABLE_ERROR_RECOVERY !== 'false'\n },\n llm: {\n provider: env.LLM_PROVIDER || 'deepseek',\n apiKey: env.LLM_API_KEY,\n apiUrl: env.LLM_API_URL || 'https://api.deepseek.com/v1/chat/completions',\n model: env.LLM_MODEL || 'deepseek-chat',\n maxTokens: parseInt(env.LLM_MAX_TOKENS || '1000', 10),\n timeout: parseInt(env.LLM_TIMEOUT || '30000', 10)\n },\n authentication: {\n maxRetries: parseInt(env.AUTH_MAX_RETRIES || '3', 10),\n retryDelay: parseInt(env.AUTH_RETRY_DELAY || '2000', 10),\n selectorTimeout: parseInt(env.AUTH_SELECTOR_TIMEOUT || '15000', 10),\n skipOnFailure: env.AUTH_SKIP_ON_FAILURE !== 'false'\n },\n visualComparison: {\n diffThreshold: parseFloat(env.VISUAL_DIFF_THRESHOLD || '0.1'),\n similarity: {\n threshold: parseFloat(env.VISUAL_SIMILARITY_THRESHOLD || '85.0')\n }\n }\n },\n };\n\n // Try to load local configuration file (optional)\n const localConfigPath = join(__dirname, '../../config.local.js');\n let localConfig = {};\n \n try {\n if (existsSync(localConfigPath)) {\n const localConfigModule = await import(localConfigPath);\n localConfig = localConfigModule.default || localConfigModule;\n }\n } catch (error) {\n // Local config is optional, ignore errors\n }\n\n // Merge configurations (environment takes precedence)\n const mergedConfig = mergeDeep(baseConfig, localConfig);\n\n // Validate basic requirements\n if (isNaN(mergedConfig.server.port) || mergedConfig.server.port < 1024 || mergedConfig.server.port > 65535) {\n throw new Error(`Invalid port: ${mergedConfig.server.port}. Must be between 1024 and 65535.`);\n }\n\n cachedConfig = mergedConfig;\n configLoadTime = Date.now();\n return cachedConfig;\n}\n\n/**\n * Get Figma API key from environment or throw error\n */\nexport async function getFigmaApiKey() {\n const config = await loadConfig();\n const apiKey = config.figma.apiKey;\n \n if (!apiKey) {\n throw new Error(\n 'Figma API key not found. Please set FIGMA_API_KEY environment variable.'\n );\n }\n \n return apiKey;\n}\n\n/**\n * Deep merge two objects\n */\nfunction mergeDeep(target, source) {\n const result = { ...target };\n \n for (const key in source) {\n if (source[key] && typeof source[key] === 'object' && !Array.isArray(source[key])) {\n result[key] = mergeDeep(target[key] || {}, source[key]);\n } else if (source[key] !== undefined) {\n result[key] = source[key];\n }\n }\n \n return result;\n}\n\n// Create singleton instance\nlet configPromise = null;\n\n/**\n * Get configuration synchronously (for compatibility)\n */\nexport function getConfig() {\n if (!configPromise) {\n configPromise = loadConfig();\n }\n return configPromise;\n}\n\n// Legacy compatibility - note this returns a promise now\nexport const config = getConfig();\nexport default getConfig; "}, "5604a10eb326fa9a7ee5a2994e513ae4735cb1ae": {"t": 1785766944.3238285, "n": 6567, "h": "e45653f7507edf26", "c": "/**\n * Unified Configuration System\n * Single source of truth for all application configuration\n */\n\nimport { readFileSync, existsSync } from 'fs';\nimport { join, dirname } from 'path';\nimport { fileURLToPath } from 'url';\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = dirname(__filename);\n\nlet cachedConfig = null;\nlet configLoadTime = 0;\nconst CONFIG_CACHE_TTL = 5 * 60 * 1000; // 5 minutes\n\n/**\n * Load configuration from environment variables and optional local config\n */\nexport async function loadConfig() {\n // Return cached config if still valid\n if (cachedConfig && Date.now() - configLoadTime < CONFIG_CACHE_TTL) {\n return cachedConfig;\n }\n\n const env = process.env;\n \n // Base configuration from environment\n const baseConfig = {\n server: {\n port: parseInt(env.PORT || env.SERVER_PORT || '3007', 10),\n host: env.HOST || env.SERVER_HOST || 'localhost',\n },\n cors: {\n origins: env.CORS_ORIGINS ? env.CORS_ORIGINS.split(',') : [\n 'http://localhost:3000',\n 'http://localhost:3007',\n 'http://localhost:5173',\n 'http://localhost:5174',\n ],\n credentials: env.CORS_CREDENTIALS !== 'false',\n },\n mcp: {\n enabled: env.MCP_ENABLED !== 'false',\n url: env.MCP_URL || 'http://127.0.0.1:3845',\n endpoint: env.MCP_ENDPOINT || '/mcp',\n },\n puppeteer: {\n headless: env.PUPPETEER_HEADLESS === 'false' ? false : \n env.PUPPETEER_HEADLESS === 'true' ? true : 'new',\n timeout: parseInt(env.PUPPETEER_TIMEOUT || '30000', 10),\n protocolTimeout: parseInt(env.PUPPETEER_PROTOCOL_TIMEOUT || '60000', 10),\n executablePath: env.PUPPETEER_EXECUTABLE_PATH || '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',\n args: env.PUPPETEER_ARGS ? env.PUPPETEER_ARGS.split(',') : [\n '--no-sandbox',\n '--disable-setuid-sandbox',\n '--disable-blink-features=AutomationControlled',\n '--disable-features=VizDisplayCompositor',\n '--disable-dev-shm-usage',\n '--disable-gpu',\n '--no-first-run',\n '--disable-background-timer-throttling',\n '--disable-backgrounding-occluded-windows',\n '--disable-renderer-backgrounding',\n ],\n },\n thresholds: {\n colorDifference: parseInt(env.COLOR_DIFFERENCE_THRESHOLD || '10', 10),\n sizeDifference: parseInt(env.SIZE_DIFFERENCE_THRESHOLD || '5', 10),\n spacingDifference: parseInt(env.SPACING_DIFFERENCE_THRESHOLD || '3', 10),\n fontSizeDifference: parseInt(env.FONT_SIZE_DIFFERENCE_THRESHOLD || '2', 10),\n },\n timeouts: {\n figmaExtraction: parseInt(env.FIGMA_EXTRACTION_TIMEOUT || '60000', 10),\n webExtraction: parseInt(env.WEB_EXTRACTION_TIMEOUT || '30000', 10),\n comparison: parseInt(env.COMPARISON_TIMEOUT || '10000', 10),\n },\n security: {\n allowedHosts: env.ALLOWED_HOSTS ? env.ALLOWED_HOSTS.split(',') : [],\n rateLimit: {\n windowMs: parseInt(env.RATE_LIMIT_WINDOW_MS || String(15 * 60 * 1000), 10),\n max: parseInt(env.RATE_LIMIT_MAX || '100', 10),\n extractionMax: parseInt(env.RATE_LIMIT_EXTRACTION_MAX || '10', 10),\n },\n },\n figma: {\n apiKey: env.FIGMA_API_KEY || env.FIGMA_PERSONAL_ACCESS_TOKEN,\n },\n // Next Version Features (Production-Ready)\n nextVersion: {\n enabled: env.ENABLE_NEXT_VERSION === 'true',\n features: {\n enhancedVisualComparison: env.ENABLE_VISUAL_COMPARISON !== 'false',\n llmIntegration: env.ENABLE_LLM_INTEGRATION === 'true',\n multiPageComparison: env.ENABLE_MULTI_PAGE === 'true',\n performanceDashboard: env.ENABLE_PERFORMANCE_DASHBOARD === 'true',\n autoErrorRecovery: env.ENABLE_ERROR_RECOVERY !== 'false'\n },\n llm: {\n provider: env.LLM_PROVIDER || 'deepseek',\n apiKey: env.LLM_API_KEY,\n apiUrl: env.LLM_API_URL || 'https://api.deepseek.com/v1/chat/completions',\n model: env.LLM_MODEL || 'deepseek-chat',\n maxTokens: parseInt(env.LLM_MAX_TOKENS || '1000', 10),\n timeout: parseInt(env.LLM_TIMEOUT || '30000', 10)\n },\n authentication: {\n maxRetries: parseInt(env.AUTH_MAX_RETRIES || '3', 10),\n retryDelay: parseInt(env.AUTH_RETRY_DELAY || '2000', 10),\n selectorTimeout: parseInt(env.AUTH_SELECTOR_TIMEOUT || '15000', 10),\n skipOnFailure: env.AUTH_SKIP_ON_FAILURE !== 'false'\n },\n visualComparison: {\n diffThreshold: parseFloat(env.VISUAL_DIFF_THRESHOLD || '0.1'),\n similarity: {\n threshold: parseFloat(env.VISUAL_SIMILARITY_THRESHOLD || '85.0')\n }\n }\n },\n };\n\n // Try to load local configuration file (optional)\n const localConfigPath = join(__dirname, '../../config.local.js');\n let localConfig = {};\n \n try {\n if (existsSync(localConfigPath)) {\n const localConfigModule = await import(localConfigPath);\n localConfig = localConfigModule.default || localConfigModule;\n }\n } catch (error) {\n // Local config is optional, ignore errors\n }\n\n // Merge configurations (environment takes precedence)\n const mergedConfig = mergeDeep(baseConfig, localConfig);\n\n // Validate basic requirements\n if (isNaN(mergedConfig.server.port) || mergedConfig.server.port < 1024 || mergedConfig.server.port > 65535) {\n throw new Error(`Invalid port: ${mergedConfig.server.port}. Must be between 1024 and 65535.`);\n }\n\n cachedConfig = mergedConfig;\n configLoadTime = Date.now();\n return cachedConfig;\n}\n\n/**\n * Get Figma API key from environment or throw error\n */\nexport async function getFigmaApiKey() {\n const config = await loadConfig();\n const apiKey = config.figma.apiKey;\n \n if (!apiKey) {\n throw new Error(\n 'Figma API key not found. Please set FIGMA_API_KEY environment variable.'\n );\n }\n \n return apiKey;\n}\n\n/**\n * Deep merge two objects\n */\nfunction mergeDeep(target, source) {\n const result = { ...target };\n \n for (const key in source) {\n if (source[key] && typeof source[key] === 'object' && !Array.isArray(source[key])) {\n result[key] = mergeDeep(target[key] || {}, source[key]);\n } else if (source[key] !== undefined) {\n result[key] = source[key];\n }\n }\n \n return result;\n}\n\n// Create singleton instance\nlet configPromise = null;\n\n/**\n * Get configuration synchronously (for compatibility)\n */\nexport function getConfig() {\n if (!configPromise) {\n configPromise = loadConfig();\n }\n return configPromise;\n}\n\n// Legacy compatibility - note this returns a promise now\nexport const config = getConfig();\nexport default getConfig; "}, "8fdc3d3d6bf36fc4082b270f8fc6bab142d71220": {"t": 1785766945.81692, "n": 5171, "h": "b343d0a9c6d0db85", "c": "/**\n * Unified Configuration System\n * Single source of truth for all application configuration\n */\n\nimport { readFileSync, existsSync } from 'fs';\nimport { join, dirname } from 'path';\nimport { fileURLToPath } from 'url';\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = dirname(__filename);\n\nlet cachedConfig = null;\nlet configLoadTime = 0;\nconst CONFIG_CACHE_TTL = 5 * 60 * 1000; // 5 minutes\n\n/**\n * Load configuration from environment variables and optional local config\n */\nexport async function loadConfig() {\n // Return cached config if still valid\n if (cachedConfig && Date.now() - configLoadTime < CONFIG_CACHE_TTL) {\n return cachedConfig;\n }\n\n const env = process.env;\n \n // Base configuration from environment\n const baseConfig = {\n server: {\n port: parseInt(env.PORT || env.SERVER_PORT || '3007', 10),\n host: env.HOST || env.SERVER_HOST || 'localhost',\n },\n cors: {\n origins: env.CORS_ORIGINS ? env.CORS_ORIGINS.split(',') : [\n 'http://localhost:3000',\n 'http://localhost:3007',\n 'http://localhost:5173',\n ],\n credentials: env.CORS_CREDENTIALS !== 'false',\n },\n mcp: {\n enabled: env.MCP_ENABLED !== 'false',\n url: env.MCP_URL || 'http://127.0.0.1:3845',\n endpoint: env.MCP_ENDPOINT || '/mcp',\n },\n puppeteer: {\n headless: env.PUPPETEER_HEADLESS === 'false' ? false : \n env.PUPPETEER_HEADLESS === 'true' ? true : 'new',\n timeout: parseInt(env.PUPPETEER_TIMEOUT || '30000', 10),\n protocolTimeout: parseInt(env.PUPPETEER_PROTOCOL_TIMEOUT || '60000', 10),\n executablePath: env.PUPPETEER_EXECUTABLE_PATH || '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',\n args: env.PUPPETEER_ARGS ? env.PUPPETEER_ARGS.split(',') : [\n '--no-sandbox',\n '--disable-setuid-sandbox',\n '--disable-blink-features=AutomationControlled',\n '--disable-features=VizDisplayCompositor',\n '--disable-dev-shm-usage',\n '--disable-gpu',\n '--no-first-run',\n '--disable-background-timer-throttling',\n '--disable-backgrounding-occluded-windows',\n '--disable-renderer-backgrounding',\n ],\n },\n thresholds: {\n colorDifference: parseInt(env.COLOR_DIFFERENCE_THRESHOLD || '10', 10),\n sizeDifference: parseInt(env.SIZE_DIFFERENCE_THRESHOLD || '5', 10),\n spacingDifference: parseInt(env.SPACING_DIFFERENCE_THRESHOLD || '3', 10),\n fontSizeDifference: parseInt(env.FONT_SIZE_DIFFERENCE_THRESHOLD || '2', 10),\n },\n timeouts: {\n figmaExtraction: parseInt(env.FIGMA_EXTRACTION_TIMEOUT || '60000', 10),\n webExtraction: parseInt(env.WEB_EXTRACTION_TIMEOUT || '30000', 10),\n comparison: parseInt(env.COMPARISON_TIMEOUT || '10000', 10),\n },\n security: {\n allowedHosts: env.ALLOWED_HOSTS ? env.ALLOWED_HOSTS.split(',') : [],\n rateLimit: {\n windowMs: parseInt(env.RATE_LIMIT_WINDOW_MS || String(15 * 60 * 1000), 10),\n max: parseInt(env.RATE_LIMIT_MAX || '100', 10),\n extractionMax: parseInt(env.RATE_LIMIT_EXTRACTION_MAX || '10', 10),\n },\n },\n figma: {\n apiKey: env.FIGMA_API_KEY || env.FIGMA_PERSONAL_ACCESS_TOKEN,\n },\n };\n\n // Try to load local configuration file (optional)\n const localConfigPath = join(__dirname, '../../config.local.js');\n let localConfig = {};\n \n try {\n if (existsSync(localConfigPath)) {\n const localConfigModule = await import(localConfigPath);\n localConfig = localConfigModule.default || localConfigModule;\n }\n } catch (error) {\n // Local config is optional, ignore errors\n }\n\n // Merge configurations (environment takes precedence)\n const mergedConfig = mergeDeep(baseConfig, localConfig);\n\n // Validate basic requirements\n if (isNaN(mergedConfig.server.port) || mergedConfig.server.port < 1024 || mergedConfig.server.port > 65535) {\n throw new Error(`Invalid port: ${mergedConfig.server.port}. Must be between 1024 and 65535.`);\n }\n\n cachedConfig = mergedConfig;\n configLoadTime = Date.now();\n return cachedConfig;\n}\n\n/**\n * Get Figma API key from environment or throw error\n */\nexport async function getFigmaApiKey() {\n const config = await loadConfig();\n const apiKey = config.figma.apiKey;\n \n if (!apiKey) {\n throw new Error(\n 'Figma API key not found. Please set FIGMA_API_KEY environment variable.'\n );\n }\n \n return apiKey;\n}\n\n/**\n * Deep merge two objects\n */\nfunction mergeDeep(target, source) {\n const result = { ...target };\n \n for (const key in source) {\n if (source[key] && typeof source[key] === 'object' && !Array.isArray(source[key])) {\n result[key] = mergeDeep(target[key] || {}, source[key]);\n } else if (source[key] !== undefined) {\n result[key] = source[key];\n }\n }\n \n return result;\n}\n\n// Create singleton instance\nlet configPromise = null;\n\n/**\n * Get configuration synchronously (for compatibility)\n */\nexport function getConfig() {\n if (!configPromise) {\n configPromise = loadConfig();\n }\n return configPromise;\n}\n\n// Legacy compatibility - note this returns a promise now\nexport const config = getConfig();\nexport default getConfig; "}}}}