All files / server/services/compiler temp-fs.ts

86.11% Statements 31/36
75% Branches 6/8
80% Functions 4/5
85.29% Lines 29/34

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 99 100 101 102 103 104 105 106        9x             104x 104x   104x 108x   108x 102x 102x   6x         6x 4x   4x 4x     4x                     4x             6x 4x           6x                           21x 21x 20x 20x 20x   1x                           52x 52x       52x 52x 52x            
import { mkdir, rm, rename } from "node:fs/promises";
import { randomUUID } from "node:crypto";
import { Logger } from "@shared/logger";
 
const logger = new Logger("TempFS");
 
/**
 * Robust cleanup function that handles file locking on Windows/Unix.
 * Uses rename-before-delete to work around EPERM and EBUSY errors.
 */
export async function robustCleanupDir(dirPath: string): Promise<void> {
  const maxRetries = 3;
  const retryDelayMs = 100;
 
  for (let attempt = 0; attempt < maxRetries; attempt++) {
    try {
      // Fast path: try direct deletion first
      await rm(dirPath, { recursive: true, force: true });
      logger.debug(`Successfully deleted ${dirPath}`);
      return;
    } catch (directError) {
      logger.debug(
        `Direct delete failed (attempt ${attempt + 1}/${maxRetries}): ${directError}`,
      );
 
      // If not the last attempt, try the rename-trick
      if (attempt < maxRetries - 1) {
        try {
          // Rename to a trash path to work around file locks
          const trashPath = `${dirPath}.trash.${randomUUID()}`;
          logger.debug(
            `Attempting rename-before-delete: ${dirPath} -> ${trashPath}`,
          );
          await rename(dirPath, trashPath);
 
          // Try to delete the trash path in the background (non-blocking)
          rm(trashPath, { recursive: true, force: true }).catch((trashError) => {
            logger.warn(
              `Failed to delete trash directory ${trashPath}: ${trashError}`,
            );
            // This is non-critical; we got the original dir out of the way
          });
          return;
        } catch (renameError) {
          logger.debug(`Rename-trick failed: ${renameError}`);
          // Fall through to next retry or throw
        }
      }
    }
 
    // If not the last attempt, wait before retrying
    if (attempt < maxRetries - 1) {
      await new Promise((resolve) => setTimeout(resolve, retryDelayMs));
    }
  }
 
  // Last resort: log a warning but don't throw
  // The OS will clean up temp directories eventually
  logger.warn(
    `Failed to clean up ${dirPath} after ${maxRetries} attempts. It will be cleaned up by the OS.`,
  );
}
 
/**
 * Ensures all required temporary and cache directories exist.
 */
export async function ensureTempDirs(dirs: {
  tempDir: string;
  hexCacheDir: string;
  buildCachePath: string;
  binaryStorageDir: string;
}): Promise<void> {
  try {
    await mkdir(dirs.tempDir, { recursive: true });
    await mkdir(dirs.hexCacheDir, { recursive: true });
    await mkdir(dirs.buildCachePath, { recursive: true });
    await mkdir(dirs.binaryStorageDir, { recursive: true });
  } catch (error) {
    logger.warn(
      `Failed to create temp directory: ${error instanceof Error ? error.message : error}`,
    );
  }
}
 
/**
 * Remove sketch-specific temporary directories created during compilation.
 */
export async function cleanupSketchDirs(
  sketchDir: string,
  baseTempDir: string,
  tempRoot?: string,
): Promise<void> {
  try {
    await robustCleanupDir(sketchDir);
  } catch (error) {
    logger.warn(`Failed to clean up sketch directory: ${error}`);
  }
  Eif (!tempRoot) {
    try {
      await robustCleanupDir(baseTempDir);
    } catch (error) {
      logger.warn(`Failed to remove base temp directory: ${error}`);
    }
  }
}