Files
gridpilot.gg/packages/infrastructure/config/AutomationConfig.ts
2025-11-30 23:00:48 +01:00

211 lines
7.3 KiB
TypeScript

/**
* 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<RetryConfig>;
/** Timing configuration for waits */
timing?: Partial<TimingConfig>;
};
/** 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;
}