All files / src/cli log-cleanup.ts

89.47% Statements 34/38
63.63% Branches 14/22
100% Functions 2/2
90.62% Lines 29/32

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                    14x     14x 14x   2x     12x 12x 18x 17x 17x 17x 17x 8x             12x                             5x   5x   4x 4x   2x 1x         1x           1x 1x 1x 1x 1x 1x           1x 1x     1x    
import { readdirSync, statSync, unlinkSync } from 'node:fs';
import { join } from 'node:path';
import type { LogCleanupMode } from '../config/schema.js';
import type { Reporter } from './output/reporter.js';
import { readSingleKey } from './input.js';
 
/**
 * Find .jsonl files in a directory that are older than retentionDays.
 */
export function findExpiredArtifacts(dir: string, retentionDays: number): string[] {
  const cutoff = Date.now() - retentionDays * 24 * 60 * 60 * 1000;
 
  let entries: string[];
  try {
    entries = readdirSync(dir);
  } catch {
    return [];
  }
 
  const expired: string[] = [];
  for (const entry of entries) {
    if (!entry.endsWith('.jsonl')) continue;
    const fullPath = join(dir, entry);
    try {
      const stat = statSync(fullPath);
      if (stat.mtimeMs < cutoff) {
        expired.push(fullPath);
      }
    } catch {
      // Skip files we can't stat
    }
  }
 
  return expired;
}
 
/**
 * Clean up expired .jsonl artifact files based on the configured mode.
 * Works for both log and session directories.
 * Returns the number of files deleted.
 */
export async function cleanupArtifacts(opts: {
  dir: string;
  retentionDays: number;
  mode: LogCleanupMode;
  isTTY: boolean;
  reporter: Reporter;
}): Promise<number> {
  const { dir, retentionDays, mode, isTTY, reporter } = opts;
 
  if (mode === 'never') return 0;
 
  const expired = findExpiredArtifacts(dir, retentionDays);
  if (expired.length === 0) return 0;
 
  if (mode === 'ask') {
    Eif (!isTTY || !process.stdin.isTTY) return 0;
 
    process.stderr.write(
      `Found ${expired.length} expired ${expired.length === 1 ? 'file' : 'files'} older than ${retentionDays} days. Remove? [y/N] `
    );
    const key = await readSingleKey();
    process.stderr.write(key + '\n');
 
    if (key !== 'y') return 0;
  }
 
  let deleted = 0;
  for (const filePath of expired) {
    try {
      unlinkSync(filePath);
      try { unlinkSync(`${filePath}.done`); } catch { /* sidecar may not exist */ }
      deleted++;
    } catch {
      // Skip files we can't delete
    }
  }
 
  Eif (deleted > 0) {
    reporter.debug(`Cleaned up ${deleted} expired ${deleted === 1 ? 'file' : 'files'}`);
  }
 
  return deleted;
}