File size: 2,234 Bytes
1dbc34b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
/**
 * File system utilities that handle symlinks safely
 */

import { secureFs } from '@automaker/platform';
import path from 'path';

/**
 * Create a directory, handling symlinks safely to avoid ELOOP errors.
 * If the path already exists as a directory or symlink, returns success.
 */
export async function mkdirSafe(dirPath: string): Promise<void> {
  const resolvedPath = path.resolve(dirPath);

  // Check if path already exists using lstat (doesn't follow symlinks)
  try {
    const stats = await secureFs.lstat(resolvedPath);
    // Guard: some environments (e.g. mocked fs) may return undefined
    if (stats == null) {
      // Treat as path does not exist, fall through to create
    } else if (stats.isDirectory() || stats.isSymbolicLink()) {
      // Path exists - if it's a directory or symlink, consider it success
      return;
    } else {
      // It's a file - can't create directory
      throw new Error(`Path exists and is not a directory: ${resolvedPath}`);
    }
  } catch (error: any) {
    // ENOENT means path doesn't exist - we should create it
    if (error.code !== 'ENOENT') {
      // Some other error (could be ELOOP in parent path)
      // If it's ELOOP, the path involves symlinks - don't try to create
      if (error.code === 'ELOOP') {
        console.warn(`[fs-utils] Symlink loop detected at ${resolvedPath}, skipping mkdir`);
        return;
      }
      throw error;
    }
  }

  // Path doesn't exist, create it
  try {
    await secureFs.mkdir(resolvedPath, { recursive: true });
  } catch (error: any) {
    // Handle race conditions and symlink issues
    if (error.code === 'EEXIST' || error.code === 'ELOOP') {
      return;
    }
    throw error;
  }
}

/**
 * Check if a path exists, handling symlinks safely.
 * Returns true if the path exists as a file, directory, or symlink.
 */
export async function existsSafe(filePath: string): Promise<boolean> {
  try {
    await secureFs.lstat(filePath);
    return true;
  } catch (error: any) {
    if (error.code === 'ENOENT') {
      return false;
    }
    // ELOOP or other errors - path exists but is problematic
    if (error.code === 'ELOOP') {
      return true; // Symlink exists, even if looping
    }
    throw error;
  }
}