UNPKG

@wix-pilot/core

Version:

A flexible plugin that drives your tests with human-written commands, enhanced by the power of large language models (LLMs)

228 lines (227 loc) 8.9 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.CacheHandler = void 0; const fs_1 = __importDefault(require("fs")); const path_1 = __importDefault(require("path")); const os_1 = __importDefault(require("os")); const logger_1 = __importDefault(require("../../common/logger")); const testEnvUtils_1 = require("./testEnvUtils"); /** * CacheHandler provides a unified caching solution for both StepPerformer and AutoPerformer. * It works with the SnapshotComparator to compare snapshots and find matching cache entries. */ class CacheHandler { static CACHE_DIRECTORY = "__pilot_cache__"; static DEFAULT_CACHE_FILENAME = "global.json"; static APP_NAME = "PilotAutomation"; cache = new Map(); temporaryCache = new Map(); overrideCacheFilePath; cacheFilePath; cacheOptions; snapshotComparator; /** * Creates a new CacheHandler instance * @param snapshotComparator The snapshot comparator to use for hash generation and comparison * @param cacheOptions Cache configuration options * @param cacheFilePath Optional explicit cache file path override */ constructor(snapshotComparator, cacheOptions = {}, cacheFilePath) { this.overrideCacheFilePath = cacheFilePath; this.cacheOptions = this.createCacheOptionsWithDefaults(cacheOptions); this.snapshotComparator = snapshotComparator; this.cacheFilePath = this.determineCurrentCacheFilePath(); } createCacheOptionsWithDefaults(cacheOptions) { return { shouldUseCache: cacheOptions.shouldUseCache ?? true, shouldOverrideCache: cacheOptions.shouldOverrideCache ?? false, }; } determineCurrentCacheFilePath() { return this.overrideCacheFilePath || this.getCacheFilePath(); } /** * Generate hashes for a snapshot using all registered algorithms * @param screenCapture The screen capture result * @returns Object with hash values from each registered algorithm */ async generateHashes(screenCapture) { return await this.snapshotComparator.generateHashes(screenCapture); } loadCacheFromFile() { this.cacheFilePath = this.determineCurrentCacheFilePath(); try { if (fs_1.default.existsSync(this.cacheFilePath)) { const data = fs_1.default.readFileSync(this.cacheFilePath, "utf-8"); const json = JSON.parse(data); this.cache = new Map(Object.entries(json)); } else { this.cache.clear(); } } catch (error) { logger_1.default.warn("Error loading cache from file:", { message: String(error), color: "yellow", }); this.cache.clear(); } } saveCacheToFile() { try { const dirPath = path_1.default.dirname(this.cacheFilePath); if (!fs_1.default.existsSync(dirPath)) { fs_1.default.mkdirSync(dirPath, { recursive: true }); } const json = Object.fromEntries(this.cache); fs_1.default.writeFileSync(this.cacheFilePath, JSON.stringify(json, null, 2), { flag: "w+", }); logger_1.default.info("Pilot cache saved successfully"); } catch (error) { logger_1.default.error("Error saving cache to file:", { message: String(error), color: "red", }); } } /** * Get cached values by key from the persistent cache * @param cacheKey The cache key string * @returns Array of cache values if found, undefined otherwise */ getFromPersistentCache(cacheKey) { if (this.shouldOverrideCache()) { logger_1.default.info("Cache disabled, generating new response"); return undefined; } return this.cache.get(cacheKey); } /** * Add value to temporary cache * @param cacheKey The cache key string * @param value The value to cache * @param snapshotHashes Hash values for the current snapshot */ addToTemporaryCache(cacheKey, value, snapshotHashes) { logger_1.default.labeled("CACHE").info("Saving response to cache"); const cacheValue = { value, snapshotHashes: snapshotHashes || {}, creationTime: Date.now(), }; const existingValues = this.temporaryCache.get(cacheKey) || []; this.temporaryCache.set(cacheKey, [ ...existingValues, cacheValue, ]); } /** * Persist temporary cache to permanent cache and save to file */ flushTemporaryCache() { this.temporaryCache.forEach((values, key) => { const existingValues = this.cache.get(key) || []; this.cache.set(key, [...existingValues, ...values]); }); this.saveCacheToFile(); this.clearTemporaryCache(); } /** * Clear the temporary cache without persisting it */ clearTemporaryCache() { this.temporaryCache.clear(); } /** * Find matching cache entry by comparing snapshot hashes * @param cacheValues Array of cache values to search * @param currentHashes Current snapshot hashes (complete with all algorithms) * @returns Matching cache value if found, undefined otherwise */ findMatchingCacheEntry(cacheValues, currentHashes) { if (!cacheValues?.length || !currentHashes) { return undefined; } return cacheValues.find((entry) => { return this.snapshotComparator.compareSnapshot(currentHashes, entry.snapshotHashes); }); } /** * Generate a cache key from serializable data * @param keyData The data to use as a cache key (must be JSON serializable) * @returns Cache key string or undefined if cache is disabled * @example * // Generate a key for step performer * const key = cacheHandler.generateCacheKey({ step, previousSteps }); * * // Generate a key for auto performer * const key = cacheHandler.generateCacheKey({ goal, previousSteps }); */ generateCacheKey(keyData) { if (!this.isCacheInUse()) { return undefined; } return JSON.stringify(keyData); } shouldOverrideCache() { return this.cacheOptions?.shouldOverrideCache; } isCacheInUse() { return this.cacheOptions?.shouldUseCache !== false; } /** * Gets the OS-specific user data directory path for the application * @returns The appropriate application data directory for the current OS */ getUserDataDir() { const platform = process.platform; const appName = CacheHandler.APP_NAME; switch (platform) { case "darwin": return path_1.default.join(os_1.default.homedir(), "Library", "Application Support", appName); case "win32": return process.env.APPDATA ? path_1.default.join(process.env.APPDATA, appName) : path_1.default.join(os_1.default.homedir(), "AppData", "Roaming", appName); case "linux": return path_1.default.join(os_1.default.homedir(), ".config", appName); default: return path_1.default.join(os_1.default.homedir(), ".local", "share", appName); } } /** * Determines the appropriate cache file path based on the caller path * @returns The resolved cache file path */ getCacheFilePath() { const callerPath = (0, testEnvUtils_1.getCurrentTestFilePath)(); return callerPath ? this.getCallerCacheFilePath(callerPath) : this.getDefaultCacheFilePath(); } /** * Gets the cache file path for a specific caller file (typically a Jest test) * @param callerPath The path of the calling file * @returns The resolved cache file path specific to the caller */ getCallerCacheFilePath(callerPath) { const testDir = path_1.default.dirname(callerPath); const testFilename = path_1.default.basename(callerPath, path_1.default.extname(callerPath)); return path_1.default.join(testDir, CacheHandler.CACHE_DIRECTORY, `${testFilename}.json`); } /** * Gets the default global cache file path when no caller path is available * @returns The resolved default cache file path */ getDefaultCacheFilePath() { return path_1.default.join(this.getUserDataDir(), CacheHandler.CACHE_DIRECTORY, CacheHandler.DEFAULT_CACHE_FILENAME); } } exports.CacheHandler = CacheHandler;