/** * Automation configuration module for environment-based adapter selection. * * This module provides configuration types and loaders for the automation system, * allowing switching between different adapters based on NODE_ENV. * * Mapping: * - NODE_ENV=production → real browser automation → iRacing Window → Image Templates * - NODE_ENV=development → real browser automation → iRacing Window → Image Templates * - NODE_ENV=test → MockBrowserAutomation → N/A → N/A */ export type AutomationMode = 'production' | 'development' | 'test'; /** * @deprecated Use AutomationMode instead. Will be removed in future version. */ export type LegacyAutomationMode = 'dev' | 'production' | 'mock'; /** * Retry configuration for element finding operations. */ export interface RetryConfig { /** Maximum number of retry attempts (default: 3) */ maxRetries: number; /** Initial delay in milliseconds before first retry (default: 500) */ baseDelayMs: number; /** Maximum delay in milliseconds between retries (default: 5000) */ maxDelayMs: number; /** Multiplier for exponential backoff (default: 2.0) */ backoffMultiplier: number; } /** * Timing configuration for automation operations. */ export interface TimingConfig { /** Wait time for page to load after opening browser (default: 5000) */ pageLoadWaitMs: number; /** Delay between sequential actions (default: 200) */ interActionDelayMs: number; /** Delay after clicking an element (default: 300) */ postClickDelayMs: number; /** Delay before starting step execution (default: 100) */ preStepDelayMs: number; } /** * Default retry configuration values. */ export const DEFAULT_RETRY_CONFIG: RetryConfig = { maxRetries: 3, baseDelayMs: 500, maxDelayMs: 5000, backoffMultiplier: 2.0, }; /** * Default timing configuration values. */ export const DEFAULT_TIMING_CONFIG: TimingConfig = { pageLoadWaitMs: 5000, interActionDelayMs: 200, postClickDelayMs: 300, preStepDelayMs: 100, }; export interface AutomationEnvironmentConfig { mode: AutomationMode; /** Production/development configuration for native automation */ nutJs?: { mouseSpeed?: number; keyboardDelay?: number; windowTitle?: string; templatePath?: string; confidence?: number; /** Retry configuration for element finding */ retry?: Partial; /** Timing configuration for waits */ timing?: Partial; }; /** Default timeout for automation operations in milliseconds */ defaultTimeout?: number; /** Number of retry attempts for failed operations */ retryAttempts?: number; /** Whether to capture screenshots on error */ screenshotOnError?: boolean; } /** * Get the automation mode based on NODE_ENV. * * Mapping: * - NODE_ENV=production → 'production' * - All other values → 'test' (default) * * For backward compatibility, if AUTOMATION_MODE is explicitly set, * it will be used with a deprecation warning logged to console. * * @returns AutomationMode derived from NODE_ENV */ export function getAutomationMode(): AutomationMode { const legacyMode = process.env.AUTOMATION_MODE; if (legacyMode && isValidLegacyAutomationMode(legacyMode)) { console.warn( `[DEPRECATED] AUTOMATION_MODE environment variable is deprecated. ` + `Use NODE_ENV instead. Mapping: dev→test, mock→test, production→production` ); return mapLegacyMode(legacyMode); } const nodeEnv = process.env.NODE_ENV; // Map NODE_ENV to AutomationMode if (nodeEnv === 'production') return 'production'; if (nodeEnv === 'development') return 'development'; return 'test'; } /** * Load automation configuration from environment variables. * * Environment variables: * - NODE_ENV: 'production' | 'test' (default: 'test') * - AUTOMATION_MODE: (deprecated) 'dev' | 'production' | 'mock' * - IRACING_WINDOW_TITLE: Window title for native automation (default: 'iRacing') * - TEMPLATE_PATH: Path to template images (default: './resources/templates') * - OCR_CONFIDENCE: OCR confidence threshold (default: 0.9) * - AUTOMATION_TIMEOUT: Default timeout in ms (default: 30000) * - RETRY_ATTEMPTS: Number of retry attempts (default: 3) * - SCREENSHOT_ON_ERROR: Capture screenshots on error (default: true) * * @returns AutomationEnvironmentConfig with parsed environment values */ export function loadAutomationConfig(): AutomationEnvironmentConfig { const mode = getAutomationMode(); return { mode, nutJs: { mouseSpeed: parseIntSafe(process.env.NUTJS_MOUSE_SPEED, 1000), keyboardDelay: parseIntSafe(process.env.NUTJS_KEYBOARD_DELAY, 50), windowTitle: process.env.IRACING_WINDOW_TITLE || 'iRacing', templatePath: process.env.TEMPLATE_PATH || './resources/templates/iracing', confidence: parseFloatSafe(process.env.OCR_CONFIDENCE, 0.9), retry: { maxRetries: parseIntSafe(process.env.AUTOMATION_MAX_RETRIES, DEFAULT_RETRY_CONFIG.maxRetries), baseDelayMs: parseIntSafe(process.env.AUTOMATION_BASE_DELAY_MS, DEFAULT_RETRY_CONFIG.baseDelayMs), maxDelayMs: parseIntSafe(process.env.AUTOMATION_MAX_DELAY_MS, DEFAULT_RETRY_CONFIG.maxDelayMs), backoffMultiplier: parseFloatSafe(process.env.AUTOMATION_BACKOFF_MULTIPLIER, DEFAULT_RETRY_CONFIG.backoffMultiplier), }, timing: { pageLoadWaitMs: parseIntSafe(process.env.AUTOMATION_PAGE_LOAD_WAIT_MS, DEFAULT_TIMING_CONFIG.pageLoadWaitMs), interActionDelayMs: parseIntSafe(process.env.AUTOMATION_INTER_ACTION_DELAY_MS, DEFAULT_TIMING_CONFIG.interActionDelayMs), postClickDelayMs: parseIntSafe(process.env.AUTOMATION_POST_CLICK_DELAY_MS, DEFAULT_TIMING_CONFIG.postClickDelayMs), preStepDelayMs: parseIntSafe(process.env.AUTOMATION_PRE_STEP_DELAY_MS, DEFAULT_TIMING_CONFIG.preStepDelayMs), }, }, defaultTimeout: parseIntSafe(process.env.AUTOMATION_TIMEOUT, 30000), retryAttempts: parseIntSafe(process.env.RETRY_ATTEMPTS, 3), screenshotOnError: process.env.SCREENSHOT_ON_ERROR !== 'false', }; } /** * Type guard to validate automation mode string. */ function isValidAutomationMode(value: string | undefined): value is AutomationMode { return value === 'production' || value === 'development' || value === 'test'; } /** * Type guard to validate legacy automation mode string. */ function isValidLegacyAutomationMode(value: string | undefined): value is LegacyAutomationMode { return value === 'dev' || value === 'production' || value === 'mock'; } /** * Map legacy automation mode to new mode. */ function mapLegacyMode(legacy: LegacyAutomationMode): AutomationMode { switch (legacy) { case 'dev': return 'test'; case 'mock': return 'test'; case 'production': return 'production'; } } /** * Safely parse an integer with a default fallback. */ function parseIntSafe(value: string | undefined, defaultValue: number): number { if (value === undefined || value === '') { return defaultValue; } const parsed = parseInt(value, 10); return isNaN(parsed) ? defaultValue : parsed; } /** * Safely parse a float with a default fallback. */ function parseFloatSafe(value: string | undefined, defaultValue: number): number { if (value === undefined || value === '') { return defaultValue; } const parsed = parseFloat(value); return isNaN(parsed) ? defaultValue : parsed; }