@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
JavaScript
"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;