All files cache.ts

96.77% Statements 30/31
91.66% Branches 11/12
100% Functions 0/0
100% Lines 28/28

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98      2x                               2x 2x     4x   4x 4x 1x   1x         5x 5x 5x               2x 2x 2x                     3x 2x 2x 2x       2x   1x   2x                 9x       13x       2x 2x     1x       1x   2x    
import fs from 'node:fs';
import path from 'node:path';
 
export const ANALYSIS_SCHEMA_VERSION = '1.1.0'; // Keep in sync with REPORT_SCHEMA_VERSION in index.ts
 
interface CacheEntry {
  mtimeMs: number;
  sizeBytes: number;
  result: unknown;
  lastAccessMs: number;
}
 
interface AnalysisCache {
  version: number;
  schemaVersion: string;
  root: string;
  entries: Record<string, CacheEntry>;
}
 
const CACHE_VERSION = 1;
const DEFAULT_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1000; // 7 days
 
export function loadCache(root: string): AnalysisCache | null {
  const cachePath = path.join(root, '.octocode', 'scan', '.cache', 'analysis-cache.json');
  try {
    const data = JSON.parse(fs.readFileSync(cachePath, 'utf8'));
    if (data.version !== CACHE_VERSION || data.root !== root || data.schemaVersion !== ANALYSIS_SCHEMA_VERSION) return null;
    return data;
  } catch {
    return null;
  }
}
 
export function saveCache(root: string, cache: AnalysisCache): void {
  const dir = path.join(root, '.octocode', 'scan', '.cache');
  fs.mkdirSync(dir, { recursive: true });
  fs.writeFileSync(
    path.join(dir, 'analysis-cache.json'),
    JSON.stringify(cache),
    'utf8',
  );
}
 
export function clearCache(root: string): void {
  const cachePath = path.join(root, '.octocode', 'scan', '.cache', 'analysis-cache.json');
  try {
    fs.unlinkSync(cachePath);
  } catch {
    /* Cache file may not exist; ignore */
  }
}
 
export function isCacheHit(
  cache: AnalysisCache | null,
  relPath: string,
  stat: { mtimeMs: number; size: number },
): boolean {
  if (!cache) return false;
  const entry = cache.entries[relPath];
  Iif (!entry) return false;
  return entry.mtimeMs === stat.mtimeMs && entry.sizeBytes === stat.size;
}
 
export function getCachedResult(cache: AnalysisCache, relPath: string): unknown {
  const entry = cache.entries[relPath];
  if (entry) {
    entry.lastAccessMs = Date.now();
  }
  return entry?.result;
}
 
export function setCacheEntry(
  cache: AnalysisCache,
  relPath: string,
  stat: { mtimeMs: number; size: number },
  result: unknown,
): void {
  cache.entries[relPath] = { mtimeMs: stat.mtimeMs, sizeBytes: stat.size, result, lastAccessMs: Date.now() };
}
 
export function createEmptyCache(root: string): AnalysisCache {
  return { version: CACHE_VERSION, schemaVersion: ANALYSIS_SCHEMA_VERSION, root, entries: {} };
}
 
export function garbageCollect(cache: AnalysisCache, maxAgeMs: number = DEFAULT_MAX_AGE_MS): number {
  const now = Date.now();
  const keysToRemove: string[] = [];
  for (const [key, entry] of Object.entries(cache.entries)) {
    if (now - entry.lastAccessMs > maxAgeMs) {
      keysToRemove.push(key);
    }
  }
  for (const key of keysToRemove) {
    delete cache.entries[key];
  }
  return keysToRemove.length;
}