Skip to content

Commit

Permalink
Merge pull request #165 from yamadashy/feature/dir-permission
Browse files Browse the repository at this point in the history
feat(file): Add permission checks for directory scanning
  • Loading branch information
yamadashy authored Nov 10, 2024
2 parents 6d0da85 + 5d9f7e7 commit 2d09e46
Show file tree
Hide file tree
Showing 2 changed files with 138 additions and 0 deletions.
26 changes: 26 additions & 0 deletions src/core/file/fileSearch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,19 @@ import type { RepomixConfigMerged } from '../../config/configTypes.js';
import { defaultIgnoreList } from '../../config/defaultIgnore.js';
import { logger } from '../../shared/logger.js';
import { sortPaths } from './filePathSort.js';
import { PermissionError, checkDirectoryPermissions } from './permissionCheck.js';

export const searchFiles = async (rootDir: string, config: RepomixConfigMerged): Promise<string[]> => {
// First check directory permissions
const permissionCheck = await checkDirectoryPermissions(rootDir);

if (!permissionCheck.hasPermission) {
if (permissionCheck.error instanceof PermissionError) {
throw permissionCheck.error;
}
throw new Error(`Cannot access directory ${rootDir}: ${permissionCheck.error?.message}`);
}

const includePatterns = config.include.length > 0 ? config.include : ['**/*'];

try {
Expand All @@ -25,17 +36,32 @@ export const searchFiles = async (rootDir: string, config: RepomixConfigMerged):
absolute: false,
dot: true,
followSymbolicLinks: false,
}).catch((error) => {
// Handle EPERM errors specifically
if (error.code === 'EPERM' || error.code === 'EACCES') {
throw new PermissionError(
'Permission denied while scanning directory. Please check folder access permissions for your terminal app.',
rootDir,
);
}
throw error;
});

logger.trace(`Filtered ${filePaths.length} files`);
const sortedPaths = sortPaths(filePaths);

return sortedPaths;
} catch (error: unknown) {
// Re-throw PermissionError as is
if (error instanceof PermissionError) {
throw error;
}

if (error instanceof Error) {
logger.error('Error filtering files:', error.message);
throw new Error(`Failed to filter files in directory ${rootDir}. Reason: ${error.message}`);
}

logger.error('An unexpected error occurred:', error);
throw new Error('An unexpected error occurred while filtering files.');
}
Expand Down
112 changes: 112 additions & 0 deletions src/core/file/permissionCheck.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import { constants } from 'node:fs';
import * as fs from 'node:fs/promises';
import { platform } from 'node:os';
import { logger } from '../../shared/logger.js';

export interface PermissionCheckResult {
hasPermission: boolean;
error?: Error;
details?: {
read?: boolean;
write?: boolean;
execute?: boolean;
};
}

export class PermissionError extends Error {
constructor(
message: string,
public readonly path: string,
public readonly code?: string,
) {
super(message);
this.name = 'PermissionError';
}
}

export async function checkDirectoryPermissions(dirPath: string): Promise<PermissionCheckResult> {
try {
// First try to read directory contents
await fs.readdir(dirPath);

// Check specific permissions
const details = {
read: false,
write: false,
execute: false,
};

try {
await fs.access(dirPath, constants.R_OK);
details.read = true;
} catch {}

try {
await fs.access(dirPath, constants.W_OK);
details.write = true;
} catch {}

try {
await fs.access(dirPath, constants.X_OK);
details.execute = true;
} catch {}

const hasAllPermissions = details.read && details.write && details.execute;

if (!hasAllPermissions) {
return {
hasPermission: false,
details,
};
}

return {
hasPermission: true,
details,
};
} catch (error) {
if (error instanceof Error && 'code' in error) {
switch (error.code) {
case 'EPERM':
case 'EACCES':
case 'EISDIR':
return {
hasPermission: false,
error: new PermissionError(getMacOSPermissionMessage(dirPath, error.code), dirPath, error.code),
};
default:
logger.debug('Directory permission check error:', error);
return {
hasPermission: false,
error: error as Error,
};
}
}
return {
hasPermission: false,
error: error instanceof Error ? error : new Error(String(error)),
};
}
}

function getMacOSPermissionMessage(dirPath: string, errorCode?: string): string {
if (platform() === 'darwin') {
return `Permission denied: Cannot access '${dirPath}', error code: ${errorCode}.
This error often occurs when macOS security restrictions prevent access to the directory.
To fix this:
1. Open System Settings
2. Navigate to Privacy & Security > Files and Folders
3. Find your terminal app (Terminal.app, iTerm2, VS Code, etc.)
4. Grant necessary folder access permissions
If your terminal app is not listed:
- Try running repomix command again
- When prompted by macOS, click "Allow"
- Restart your terminal app if needed
`;
}

return `Permission denied: Cannot access '${dirPath}'`;
}

0 comments on commit 2d09e46

Please sign in to comment.