* Symlinks directories from the main repository to avoid duplication. * This prevents disk bloat from duplicating node_modules and other large directories. * * @param repoRootPath - Path to the main repository root * @param worktreePath - Path to the worktree directory * @param dirsToSymlink -
( repoRootPath: string, worktreePath: string, dirsToSymlink: string[], )
| 100 | * @param dirsToSymlink - Array of directory names to symlink (e.g., ['node_modules']) |
| 101 | */ |
| 102 | async function symlinkDirectories( |
| 103 | repoRootPath: string, |
| 104 | worktreePath: string, |
| 105 | dirsToSymlink: string[], |
| 106 | ): Promise<void> { |
| 107 | for (const dir of dirsToSymlink) { |
| 108 | // Validate directory doesn't escape repository boundaries |
| 109 | if (containsPathTraversal(dir)) { |
| 110 | logForDebugging( |
| 111 | `Skipping symlink for "${dir}": path traversal detected`, |
| 112 | { level: 'warn' }, |
| 113 | ) |
| 114 | continue |
| 115 | } |
| 116 | |
| 117 | const sourcePath = join(repoRootPath, dir) |
| 118 | const destPath = join(worktreePath, dir) |
| 119 | |
| 120 | try { |
| 121 | await symlink(sourcePath, destPath, 'dir') |
| 122 | logForDebugging( |
| 123 | `Symlinked ${dir} from main repository to worktree to avoid disk bloat`, |
| 124 | ) |
| 125 | } catch (error) { |
| 126 | const code = getErrnoCode(error) |
| 127 | // ENOENT: source doesn't exist yet (expected - skip silently) |
| 128 | // EEXIST: destination already exists (expected - skip silently) |
| 129 | if (code !== 'ENOENT' && code !== 'EEXIST') { |
| 130 | // Unexpected error (e.g., permission denied, unsupported platform) |
| 131 | logForDebugging( |
| 132 | `Failed to symlink ${dir} (${code ?? 'unknown'}): ${errorMessage(error)}`, |
| 133 | { level: 'warn' }, |
| 134 | ) |
| 135 | } |
| 136 | } |
| 137 | } |
| 138 | } |
| 139 | |
| 140 | export type WorktreeSession = { |
| 141 | originalCwd: string |
no test coverage detected