Function bodies 337 total
BinaryTransformer.canTransform method · javascript · L210-L217 (8 LOC)src/transforms/transformers/BinaryTransformer.js
canTransform(file) {
// Check if file is marked as binary
if (file.isBinary) return true;
// Check extension
const ext = (file.path || '').toLowerCase().match(/\.[^.]+$/);
return ext && this.supportedExtensions.includes(ext[0]);
}BinaryTransformer.validateInput method · javascript · L219-L225 (7 LOC)src/transforms/transformers/BinaryTransformer.js
validateInput(file) {
super.validateInput(file);
if (!file.absolutePath && this.config.get('copytree.binaryFileAction') === 'base64') {
throw new Error('File absolute path is required for base64 encoding');
}
}FileLoaderTransformer.doTransform method · javascript · L15-L66 (52 LOC)src/transforms/transformers/FileLoaderTransformer.js
async doTransform(file) {
// If content is already loaded, return as-is
if (file.content !== undefined) {
return file;
}
// Load content from disk
try {
let content;
if (file.isBinary) {
// For binary files, return placeholder or base64 based on config
const binaryAction = this.config.get('copytree.binaryFileAction', 'placeholder');
switch (binaryAction) {
case 'base64': {
const buffer = await fs.readFile(file.absolutePath);
content = buffer.toString('base64');
break;
}
case 'skip':
return null;
case 'placeholder':
default:
content = this.config.get(
'copytree.binaryPlaceholderText',
'[Binary file not included]',
);
}
} else {
// Load text content
content = await fs.readFile(file.absolutePath, this.encoding);
}
return {
..FileLoaderTransformer.validateInput method · javascript · L68-L74 (7 LOC)src/transforms/transformers/FileLoaderTransformer.js
validateInput(file) {
super.validateInput(file);
if (!file.absolutePath) {
throw new Error('File absolute path is required');
}
}StreamingFileLoaderTransformer.constructor method · javascript · L10-L16 (7 LOC)src/transforms/transformers/StreamingFileLoaderTransformer.js
constructor(options = {}) {
super(options);
this.description = 'Loads file content using streams for memory efficiency';
this.bufferSize = options.bufferSize || 64 * 1024; // 64KB chunks
this.maxSize = options.maxSize || this.config.get('copytree.maxFileSize', 10 * 1024 * 1024);
this.encoding = options.encoding || 'utf8';
}StreamingFileLoaderTransformer.doTransform method · javascript · L18-L93 (76 LOC)src/transforms/transformers/StreamingFileLoaderTransformer.js
async doTransform(file) {
try {
// Skip if already has content
if (file.content !== undefined) {
return file;
}
// Check file size
const stats = file.stats || (await fs.stat(file.absolutePath));
if (stats.size > this.maxSize) {
return {
...file,
content: `[File too large: ${this.formatBytes(stats.size)}]`,
isBinary: false,
transformed: true,
transformedBy: this.constructor.name,
streamingSkipped: true,
};
}
// Check if binary
const isBinary = await this.isBinaryFile(file.absolutePath, stats.size);
if (isBinary) {
const binaryAction = this.config.get('copytree.binaryFileAction', 'placeholder');
if (binaryAction === 'skip') {
return null;
} else if (binaryAction === 'base64') {
// Use streaming for base64 encoding
const content = await this.streamToBase64(file.absolutePath);
StreamingFileLoaderTransformer.streamTextFile method · javascript · L98-L110 (13 LOC)src/transforms/transformers/StreamingFileLoaderTransformer.js
async streamTextFile(filePath) {
return new Promise((resolve, reject) => {
const chunks = [];
const stream = fs.createReadStream(filePath, {
encoding: this.encoding,
highWaterMark: this.bufferSize,
});
stream.on('data', (chunk) => chunks.push(chunk));
stream.on('end', () => resolve(chunks.join('')));
stream.on('error', reject);
});
}Repobility · MCP-ready · https://repobility.com
StreamingFileLoaderTransformer.streamToBase64 method · javascript · L115-L129 (15 LOC)src/transforms/transformers/StreamingFileLoaderTransformer.js
async streamToBase64(filePath) {
return new Promise((resolve, reject) => {
const chunks = [];
const stream = fs.createReadStream(filePath, {
highWaterMark: this.bufferSize,
});
stream.on('data', (chunk) => chunks.push(chunk));
stream.on('end', () => {
const buffer = Buffer.concat(chunks);
resolve(buffer.toString('base64'));
});
stream.on('error', reject);
});
}StreamingFileLoaderTransformer.isBinaryFile method · javascript · L134-L231 (98 LOC)src/transforms/transformers/StreamingFileLoaderTransformer.js
async isBinaryFile(filePath, size) {
if (size === 0) return false;
// Common binary file extensions
const binaryExtensions = [
'.exe',
'.dll',
'.so',
'.dylib',
'.zip',
'.tar',
'.gz',
'.7z',
'.rar',
'.pdf',
'.doc',
'.docx',
'.xls',
'.xlsx',
'.ppt',
'.pptx',
'.odt',
'.ods',
'.odp',
'.jpg',
'.jpeg',
'.png',
'.gif',
'.bmp',
'.ico',
'.svg',
'.webp',
'.mp3',
'.mp4',
'.avi',
'.mov',
'.wmv',
'.flv',
'.webm',
'.mkv',
'.wav',
'.flac',
'.aac',
'.ogg',
'.wma',
'.ttf',
'.otf',
'.woff',
'.woff2',
'.eot',
'.pyc',
'.pyo',
'.class',
'.o',
'.obj',
'.a',
'.lib',
'.pdb',
'.idb',
'.jar',
'.war',
'.ear',
'.db',
'.sqlite',
'.sqlite3',
]categorizeByExt function · javascript · L114-L120 (7 LOC)src/utils/BinaryDetector.js
function categorizeByExt(ext) {
const lower = (ext || '').toLowerCase();
for (const [cat, list] of Object.entries(CATEGORIES)) {
if (list.includes(lower)) return cat;
}
return null;
}detect function · javascript · L130-L211 (82 LOC)src/utils/BinaryDetector.js
export async function detect(filePath, opts = {}) {
const sampleBytes = opts.sampleBytes ?? 8192;
const nonPrintableThreshold = opts.nonPrintableThreshold ?? 0.3;
const ext = path.extname(filePath);
let category = categorizeByExt(ext);
// Allocate buffer only for the sample size to avoid loading large files into memory
const buf = Buffer.alloc(sampleBytes);
let bytesRead = 0;
let fd = null;
try {
fd = await fs.open(filePath, 'r');
const result = await fs.read(fd, buf, 0, sampleBytes, 0);
bytesRead = result.bytesRead;
} catch (error) {
// File doesn't exist or can't be read
return {
isBinary: false,
category: 'text',
reason: 'error',
ext,
error: error.message,
};
} finally {
if (fd !== null) {
await fs.close(fd);
}
}
const sample = buf.subarray(0, bytesRead);
// Magic number match
for (const m of MAGIC) {
const sig = Buffer.from(m.sig);
if (sample.length >= sig.length && sampleisConvertibleDocument function · javascript · L219-L224 (6 LOC)src/utils/BinaryDetector.js
export function isConvertibleDocument(category, ext) {
if (category !== 'document') return false;
return ['.pdf', '.doc', '.docx', '.odt', '.rtf', '.epub', '.html', '.htm'].includes(
(ext || '').toLowerCase(),
);
}Clipboard.copyFileReference method · javascript · L27-L79 (53 LOC)src/utils/clipboard.js
static async copyFileReference(filePath) {
if (process.platform === 'win32') {
try {
// Use Windows Forms SetFileDropList via Base64-encoded PowerShell command.
// This bypasses all shell quoting (Base64 encoding) and path glob expansion
// (StringCollection is passed to SetFileDropList without any wildcard interpretation).
// Single quotes are doubled for the PS single-quoted string literal.
const psPath = filePath.replace(/'/g, "''");
const psCommand = [
'Add-Type -AssemblyName System.Windows.Forms',
'$fc = New-Object System.Collections.Specialized.StringCollection',
`[void]$fc.Add('${psPath}')`,
'[System.Windows.Forms.Clipboard]::SetFileDropList($fc)',
].join('; ');
const encoded = Buffer.from(psCommand, 'utf16le').toString('base64');
execSync(`powershell -NoProfile -NonInteractive -EncodedCommand ${encoded}`, {
stdio: 'pipe',
});
} catcClipboard.revealInFinder method · javascript · L86-L115 (30 LOC)src/utils/clipboard.js
static async revealInFinder(filePath) {
if (process.platform === 'win32') {
try {
// Use spawnSync with array args to avoid cmd.exe interpreting &, |, <, >, ^ in paths
spawnSync('explorer', [`/select,${filePath}`], { stdio: 'pipe' });
} catch (error) {
logger.debug('Failed to reveal file in Explorer:', error.message);
}
} else if (process.platform === 'linux') {
try {
// Use spawnSync with array args to avoid shell interpreting $, ", ` in paths
const dir = path.dirname(filePath);
spawnSync('xdg-open', [dir], { stdio: 'pipe' });
} catch (error) {
logger.debug('Failed to reveal file with xdg-open:', error.message);
}
} else if (process.platform === 'darwin') {
try {
// Use AppleScript to reveal file. spawnSync passes the script as a direct
// argument (no shell), so single quotes in filePath are safe.
const escapedFilePath = filePath.replace(/\\/g, '\\\\CopyTreeError.constructor method · javascript · L5-L14 (10 LOC)src/utils/errors.js
constructor(message, code = 'UNKNOWN_ERROR', details = {}) {
super(message);
this.name = 'CopyTreeError';
this.code = code;
this.details = details;
this.timestamp = new Date().toISOString();
// Capture stack trace
Error.captureStackTrace(this, this.constructor);
}Repobility's GitHub App fixes findings like these · https://github.com/apps/repobility-bot
CopyTreeError.toJSON method · javascript · L16-L25 (10 LOC)src/utils/errors.js
toJSON() {
return {
name: this.name,
message: this.message,
code: this.code,
details: this.details,
timestamp: this.timestamp,
stack: this.stack,
};
}FileSystemError.constructor method · javascript · L43-L48 (6 LOC)src/utils/errors.js
constructor(message, path, operation, details = {}) {
super(message, 'FILESYSTEM_ERROR', { path, operation, ...details });
this.name = 'FileSystemError';
this.path = path;
this.operation = operation;
}ValidationError.constructor method · javascript · L66-L71 (6 LOC)src/utils/errors.js
constructor(message, field, value, details = {}) {
super(message, 'VALIDATION_ERROR', { field, value, ...details });
this.name = 'ValidationError';
this.field = field;
this.value = value;
}TransformError.constructor method · javascript · L89-L94 (6 LOC)src/utils/errors.js
constructor(message, transformer, file, details = {}) {
super(message, 'TRANSFORM_ERROR', { transformer, file, ...details });
this.name = 'TransformError';
this.transformer = transformer;
this.file = file;
}SecretsDetectedError.constructor method · javascript · L135-L141 (7 LOC)src/utils/errors.js
constructor(secretsCount, findings = [], details = {}) {
const message = `Secrets detected: ${secretsCount} secret(s) found`;
super(message, 'SECRETS_DETECTED', { secretsCount, findings, ...details });
this.name = 'SecretsDetectedError';
this.secretsCount = secretsCount;
this.findings = findings;
}handleError function · javascript · L147-L174 (28 LOC)src/utils/errors.js
function handleError(error, options = {}) {
const { exit = true, verbose = false, logger = console.error } = options;
// Convert to CopyTreeError if not already
if (!(error instanceof CopyTreeError)) {
error = new CopyTreeError(error.message, 'UNKNOWN_ERROR', {
originalError: error.name,
originalStack: error.stack,
});
}
// Log error
if (verbose) {
logger(error.toJSON());
} else {
logger(`Error: ${error.message}`);
if (error.code) {
logger(`Code: ${error.code}`);
}
}
// Exit if requested
if (exit && process.env.NODE_ENV !== 'test') {
process.exit(1);
}
return error;
}isRetryableFsError function · javascript · L227-L232 (6 LOC)src/utils/errors.js
export function isRetryableFsError(error) {
const code = error?.code || error;
// Filesystem-specific retryable codes
const fsRetryableCodes = ['EBUSY', 'EPERM', 'EACCES', 'EMFILE', 'ENFILE', 'EAGAIN', 'EIO'];
return fsRetryableCodes.includes(code);
}categorizeError function · javascript · L239-L250 (12 LOC)src/utils/errors.js
export function categorizeError(error) {
if (isRetryableError(error)) {
return 'retryable';
}
const errorCode = error.code || error.name || '';
if (NON_RETRYABLE_ERROR_CODES.includes(errorCode)) {
return 'non-retryable';
}
return 'unknown';
}Same scanner, your repo: https://repobility.com — Repobility
hashFile function · javascript · L10-L19 (10 LOC)src/utils/fileHash.js
async function hashFile(filePath, algorithm = 'sha256') {
const hash = crypto.createHash(algorithm);
const stream = fs.createReadStream(filePath);
return new Promise((resolve, reject) => {
stream.on('data', (data) => hash.update(data));
stream.on('end', () => resolve(hash.digest('hex')));
stream.on('error', reject);
});
}generateTransformCacheKey function · javascript · L38-L48 (11 LOC)src/utils/fileHash.js
function generateTransformCacheKey(file, transformerName, options = {}) {
const components = {
path: file.path,
size: file.stats?.size || 0,
mtime: file.stats?.mtime?.getTime() || 0,
transformer: transformerName,
options: JSON.stringify(options, Object.keys(options).sort()),
};
return hashContent(JSON.stringify(components));
}FileLoader.constructor method · javascript · L18-L31 (14 LOC)src/utils/fileLoader.js
constructor(options = {}) {
this.basePath = options.basePath || process.cwd();
this.includeHidden = options.includeHidden || false;
this.followSymlinks = options.followSymlinks || false;
this.maxFileSize = options.maxFileSize || 10 * 1024 * 1024; // 10MB default
this.config = options.config || {};
// Extract retry configuration with defaults
this.retryConfig = {
maxAttempts: this.config?.copytree?.fs?.retryAttempts ?? 3,
initialDelay: this.config?.copytree?.fs?.retryDelay ?? 100,
maxDelay: this.config?.copytree?.fs?.maxDelay ?? 2000,
};
}FileLoader.loadFiles method · javascript · L36-L66 (31 LOC)src/utils/fileLoader.js
async loadFiles(patterns = {}) {
const { include = ['**/*'], exclude = [] } = patterns;
try {
// Get file paths using fast-glob
const filePaths = await fastGlob(include, {
cwd: this.basePath,
ignore: exclude,
dot: this.includeHidden,
followSymbolicLinks: this.followSymlinks,
onlyFiles: true,
});
// Load file contents
const files = [];
for (const filePath of filePaths) {
const file = await this.loadFile(filePath);
if (file) {
files.push(file);
}
}
return files;
} catch (error) {
logger.error('Failed to load files', {
basePath: this.basePath,
error: error.message,
});
throw error;
}
}FileLoader.loadFile method · javascript · L71-L184 (114 LOC)src/utils/fileLoader.js
async loadFile(relativePath) {
const fullPath = path.join(this.basePath, relativePath);
try {
// Stat file with retry logic
const stats = await withFsRetry(() => fs.stat(fullPath), {
...this.retryConfig,
onRetry: ({ code }) => recordRetry(fullPath, code),
});
// Skip files that are too large
if (stats.size > this.maxFileSize) {
// Record success for stat operation even though we're skipping the file
recordSuccessAfterRetry(fullPath);
logger.warn('Skipping large file', {
path: relativePath,
size: stats.size,
maxSize: this.maxFileSize,
});
return null;
}
// Read file content with retry logic
const raw = await withFsRetry(() => fs.readFile(fullPath, 'utf8'), {
...this.retryConfig,
onRetry: ({ code }) => recordRetry(fullPath, code),
});
// Normalize CRLF to LF for cross-platform consistency
const content = raw.FileLoader.detectFileType method · javascript · L189-L263 (75 LOC)src/utils/fileLoader.js
detectFileType(filePath, content) {
const ext = path.extname(filePath).toLowerCase();
// Code files
const codeExtensions = {
'.js': 'javascript',
'.jsx': 'javascript',
'.ts': 'typescript',
'.tsx': 'typescript',
'.py': 'python',
'.rb': 'ruby',
'.php': 'php',
'.java': 'java',
'.c': 'c',
'.cpp': 'cpp',
'.cs': 'csharp',
'.go': 'go',
'.rs': 'rust',
'.swift': 'swift',
'.kt': 'kotlin',
};
if (codeExtensions[ext]) {
return codeExtensions[ext];
}
// Data files
const dataExtensions = {
'.json': 'json',
'.xml': 'xml',
'.yaml': 'yaml',
'.yml': 'yaml',
'.toml': 'toml',
'.ini': 'ini',
'.csv': 'csv',
};
if (dataExtensions[ext]) {
return dataExtensions[ext];
}
// Document files
const docExtensions = {
'.md': 'markdown',
'.mdx': 'markdown',
'.txt': 'text',
'.rst': 'restructuredteFileLoader.isTextContent method · javascript · L268-L283 (16 LOC)src/utils/fileLoader.js
isTextContent(content) {
if (typeof content !== 'string') return false;
// Check for null bytes or other binary indicators
for (let i = 0; i < Math.min(content.length, 1000); i++) {
const charCode = content.charCodeAt(i);
if (
charCode === 0 ||
(charCode < 32 && charCode !== 9 && charCode !== 10 && charCode !== 13)
) {
return false;
}
}
return true;
}recordRetry function · javascript · L19-L25 (7 LOC)src/utils/fsErrorReport.js
export function recordRetry(path, code) {
stats.totalRetries++;
const entry = stats.byPath.get(path) ?? { retries: 0, lastCode: null, status: 'pending' };
entry.retries++;
entry.lastCode = code;
stats.byPath.set(path, entry);
}All rows above produced by Repobility · https://repobility.com
recordGiveUp function · javascript · L32-L38 (7 LOC)src/utils/fsErrorReport.js
export function recordGiveUp(path, code) {
stats.failed++;
const entry = stats.byPath.get(path) ?? { retries: 0, lastCode: null, status: 'pending' };
entry.lastCode = code;
entry.status = 'failed';
stats.byPath.set(path, entry);
}recordPermanent function · javascript · L45-L51 (7 LOC)src/utils/fsErrorReport.js
export function recordPermanent(path, code) {
stats.permanent++;
const entry = stats.byPath.get(path) ?? { retries: 0, lastCode: null, status: 'pending' };
entry.lastCode = code;
entry.status = 'permanent';
stats.byPath.set(path, entry);
}recordSuccessAfterRetry function · javascript · L57-L64 (8 LOC)src/utils/fsErrorReport.js
export function recordSuccessAfterRetry(path) {
const entry = stats.byPath.get(path);
if (entry && entry.retries > 0 && entry.status === 'pending') {
stats.succeededAfterRetry++;
entry.status = 'ok';
stats.byPath.set(path, entry);
}
}summarize function · javascript · L70-L77 (8 LOC)src/utils/fsErrorReport.js
export function summarize() {
return {
totalRetries: stats.totalRetries,
succeededAfterRetry: stats.succeededAfterRetry,
failed: stats.failed,
permanent: stats.permanent,
};
}getFailedPaths function · javascript · L91-L99 (9 LOC)src/utils/fsErrorReport.js
export function getFailedPaths() {
const failed = [];
for (const [path, entry] of stats.byPath.entries()) {
if (entry.status === 'failed') {
failed.push({ path, code: entry.lastCode, retries: entry.retries });
}
}
return failed;
}getPermanentErrorPaths function · javascript · L105-L113 (9 LOC)src/utils/fsErrorReport.js
export function getPermanentErrorPaths() {
const permanent = [];
for (const [path, entry] of stats.byPath.entries()) {
if (entry.status === 'permanent') {
permanent.push({ path, code: entry.lastCode });
}
}
return permanent;
}reset function · javascript · L118-L124 (7 LOC)src/utils/fsErrorReport.js
export function reset() {
stats.totalRetries = 0;
stats.succeededAfterRetry = 0;
stats.failed = 0;
stats.permanent = 0;
stats.byPath.clear();
}GitUtils.isGitRepository method · javascript · L32-L45 (14 LOC)src/utils/GitUtils.js
async isGitRepository() {
if (this._isRepo !== null) {
return this._isRepo;
}
try {
await this.git.revparse(['--git-dir']);
this._isRepo = true;
return true;
} catch (error) {
this._isRepo = false;
return false;
}
}Repobility · MCP-ready · https://repobility.com
GitUtils.getModifiedFiles method · javascript · L51-L84 (34 LOC)src/utils/GitUtils.js
async getModifiedFiles() {
const cacheKey = 'modified-files';
const cached = this.cache.get(cacheKey);
if (cached) return cached;
try {
if (!(await this.isGitRepository())) {
throw new GitError('Not a git repository', 'getModifiedFiles');
}
// Get status
const status = await this.git.status();
// Combine all modified files
const modifiedFiles = [
...status.modified,
...status.staged,
...status.not_added,
...status.created,
];
// Remove duplicates and normalize paths
const uniqueFiles = [...new Set(modifiedFiles)].map((file) => toPosix(file));
this.cache.set(cacheKey, uniqueFiles);
this.logger.logDebug(`Found ${uniqueFiles.length} modified files`);
return uniqueFiles;
} catch (error) {
throw new GitError(`Failed to get modified files: ${error.message}`, 'getModifiedFiles', {
originalError: error,
});
}
}GitUtils.getChangedFiles method · javascript · L92-L129 (38 LOC)src/utils/GitUtils.js
async getChangedFiles(fromRef = 'HEAD', toRef = null) {
const cacheKey = `changed-files:${fromRef}:${toRef || 'working'}`;
const cached = this.cache.get(cacheKey);
if (cached) return cached;
try {
if (!(await this.isGitRepository())) {
throw new GitError('Not a git repository', 'getChangedFiles');
}
let diffSummary;
if (toRef) {
// Diff between two refs
diffSummary = await this.git.diffSummary([fromRef, toRef]);
} else {
// Diff from ref to working directory
diffSummary = await this.git.diffSummary([fromRef]);
}
const changedFiles = diffSummary.files
.filter((file) => file.file && !file.binary)
.map((file) => toPosix(file.file));
this.cache.set(cacheKey, changedFiles);
this.logger.logDebug(
`Found ${changedFiles.length} changed files between ${fromRef} and ${toRef || 'working directory'}`,
);
return changedFiles;
} catch (error) {GitUtils.getFileStatuses method · javascript · L136-L184 (49 LOC)src/utils/GitUtils.js
async getFileStatuses(files) {
if (!files || files.length === 0) {
return {};
}
try {
if (!(await this.isGitRepository())) {
return {};
}
const status = await this.git.status();
const statusMap = {};
// Build status map — simple-git returns renamed as {from, to} objects,
// so flatten those into their destination paths (strings) for matching.
const renamedPaths = (status.renamed || []).map((r) => r.to || r);
const allStatuses = {
modified: status.modified,
staged: status.staged,
not_added: status.not_added,
created: status.created,
deleted: status.deleted,
renamed: renamedPaths,
conflicted: status.conflicted,
};
files.forEach((file) => {
const posixPath = toPosix(file);
for (const [statusType, fileList] of Object.entries(allStatuses)) {
const posixList = fileList.map((f) => toPosix(String(f)));
if (posixGitUtils.getCurrentBranch method · javascript · L190-L208 (19 LOC)src/utils/GitUtils.js
async getCurrentBranch() {
const cacheKey = 'current-branch';
const cached = this.cache.get(cacheKey);
if (cached) return cached;
try {
if (!(await this.isGitRepository())) {
return null;
}
const branch = await this.git.revparse(['--abbrev-ref', 'HEAD']);
this.cache.set(cacheKey, branch);
return branch;
} catch (error) {
this.logger.warn(`Failed to get current branch: ${error.message}`);
return null;
}
}GitUtils.getLastCommit method · javascript · L214-L244 (31 LOC)src/utils/GitUtils.js
async getLastCommit() {
const cacheKey = 'last-commit';
const cached = this.cache.get(cacheKey);
if (cached) return cached;
try {
if (!(await this.isGitRepository())) {
return null;
}
const log = await this.git.log({ n: 1 });
const commit = log.latest;
if (commit) {
const commitInfo = {
hash: commit.hash,
message: commit.message,
author: commit.author_name,
date: commit.date,
};
this.cache.set(cacheKey, commitInfo);
return commitInfo;
}
return null;
} catch (error) {
this.logger.warn(`Failed to get last commit: ${error.message}`);
return null;
}
}GitUtils.hasUncommittedChanges method · javascript · L250-L262 (13 LOC)src/utils/GitUtils.js
async hasUncommittedChanges() {
try {
if (!(await this.isGitRepository())) {
return false;
}
const status = await this.git.status();
return !status.isClean();
} catch (error) {
this.logger.warn(`Failed to check uncommitted changes: ${error.message}`);
return false;
}
}retry function · javascript · L34-L62 (29 LOC)src/utils/helpers.js
async function retry(fn, options = {}) {
const {
maxAttempts = 3,
initialDelay = 1000,
maxDelay = 10000,
backoffMultiplier = 2,
shouldRetry = (error) => true,
} = options;
let lastError;
let delay = initialDelay;
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
try {
return await fn(attempt);
} catch (error) {
lastError = error;
if (attempt === maxAttempts || !shouldRetry(error)) {
throw error;
}
await sleep(Math.min(delay, maxDelay));
delay *= backoffMultiplier;
}
}
throw lastError;
}getTempDir function · javascript · L75-L83 (9 LOC)src/utils/helpers.js
async function getTempDir(prefix = 'copytree') {
const tempBase = os.tmpdir();
const tempDir = path.join(
tempBase,
`${prefix}-${Date.now()}-${shortHash(Math.random().toString())}`,
);
await ensureDir(tempDir);
return tempDir;
}Repobility's GitHub App fixes findings like these · https://github.com/apps/repobility-bot
cleanupTempDir function · javascript · L88-L94 (7 LOC)src/utils/helpers.js
async function cleanupTempDir(tempDir) {
try {
await fs.remove(tempDir);
} catch (error) {
// Ignore cleanup errors
}
}isBinaryExtension function · javascript · L122-L169 (48 LOC)src/utils/helpers.js
function isBinaryExtension(filePath) {
const binaryExtensions = new Set([
'exe',
'dll',
'so',
'dylib',
'bin',
'jpg',
'jpeg',
'png',
'gif',
'bmp',
'ico',
'webp',
'svg',
'mp3',
'mp4',
'avi',
'mov',
'wmv',
'flv',
'webm',
'zip',
'tar',
'gz',
'rar',
'7z',
'bz2',
'pdf',
'doc',
'docx',
'xls',
'xlsx',
'ppt',
'pptx',
'ttf',
'otf',
'woff',
'woff2',
'eot',
'db',
'sqlite',
'sqlite3',
]);
const ext = getExtension(filePath).toLowerCase();
return binaryExtensions.has(ext);
}isImageExtension function · javascript · L176-L192 (17 LOC)src/utils/helpers.js
function isImageExtension(filePath) {
const imageExtensions = new Set([
'jpg',
'jpeg',
'png',
'gif',
'bmp',
'ico',
'webp',
'svg',
'tiff',
'tif',
]);
const ext = getExtension(filePath).toLowerCase();
return imageExtensions.has(ext);
}