@johnlindquist/file-forge
Version:
File Forge is a powerful CLI tool for deep analysis of codebases, generating markdown reports to feed AI reasoning models.
109 lines • 5.35 kB
JavaScript
import { promises as fs } from "node:fs";
import { FILE_SIZE_MESSAGE } from "./constants.js";
/**
* More robust check to determine if a file is likely binary
* by examining a sample of bytes and checking for binary characteristics
*
* @param buffer - The buffer to check
* @param filePath - Optional file path to check extension
* @returns True if the file is likely binary, false otherwise
*/
export function isBinaryFile(buffer, filePath) {
// Check file extension first if path is provided
if (filePath) {
const lowerPath = filePath.toLowerCase();
// Common text file extensions
const textExtensions = [
'.txt', '.md', '.js', '.ts', '.jsx', '.tsx', '.json', '.css', '.scss',
'.html', '.xml', '.yml', '.yaml', '.toml', '.ini', '.conf', '.sh',
'.bash', '.zsh', '.py', '.rb', '.java', '.c', '.cpp', '.h', '.cs',
'.go', '.rs', '.php', '.pl', '.sql', '.graphql', '.prisma', '.vue',
'.svelte', '.astro', '.mjs', '.cjs', '.log'
];
// If it has a known text extension, it's very likely not binary
if (textExtensions.some(ext => lowerPath.endsWith(ext))) {
return false;
}
// Common binary file extensions
const binaryExtensions = [
'.png', '.jpg', '.jpeg', '.gif', '.bmp', '.ico', '.webp', '.tiff',
'.pdf', '.zip', '.tar', '.gz', '.7z', '.rar', '.exe', '.dll', '.so',
'.dylib', '.bin', '.dat', '.db', '.sqlite', '.mp3', '.mp4', '.avi',
'.mov', '.mkv', '.wav', '.flac', '.ogg', '.woff', '.woff2', '.ttf',
'.otf', '.eot', '.class', '.pyc', '.o', '.a', '.lib', '.obj'
];
// If it has a known binary extension, it's very likely binary
if (binaryExtensions.some(ext => lowerPath.endsWith(ext))) {
return true;
}
}
// For files without recognized extensions or when path isn't provided,
// analyze content more carefully
// Sample size - check more bytes for larger files
const sampleSize = Math.min(buffer.length, 8192); // Check up to 8KB
if (sampleSize === 0)
return false; // Empty file is not binary
// Count binary characters
let binaryCharCount = 0;
for (let i = 0; i < sampleSize; i++) {
const byte = buffer[i];
// Check for null bytes or control characters (except common ones like newline, tab, etc.)
if (byte !== undefined && (byte === 0 || (byte < 32 && ![9, 10, 13].includes(byte)))) {
binaryCharCount++;
}
}
// Calculate percentage of binary characters
const binaryRatio = binaryCharCount / sampleSize;
// If more than 10% of sampled bytes are binary, consider it a binary file
// This threshold helps avoid false positives for large text files
return binaryRatio > 0.1;
}
/**
* Reads the content of a file with standardized header and
* checks: if the file exceeds maxSize or is binary.
*
* @param filePath - The absolute (or resolved) file path to read.
* @param maxSize - Maximum file size in bytes.
* @param displayPath - The path to display in the header (relative or full path).
* @param options - Optional configuration object
* @param options.skipHeader - If true, skips adding the file header
* @returns A Promise resolving to a string with the file header and either its content or an appropriate placeholder.
*/
export async function getFileContent(filePath, maxSize, displayPath, options) {
try {
const stat = await fs.stat(filePath);
if (stat.size > maxSize) {
if (process.env["DEBUG"]) {
console.log(`[DEBUG] File too large: ${filePath} (${stat.size} bytes > ${maxSize} bytes)`);
}
return options?.skipHeader
? `[${FILE_SIZE_MESSAGE(stat.size)}]`
: `================================\nFile: ${displayPath}\n================================\n[${FILE_SIZE_MESSAGE(stat.size)}]`;
}
// Read file as buffer first to check if it's binary
const buffer = await fs.readFile(filePath);
// Check if the file is binary
if (isBinaryFile(buffer, filePath)) {
if (process.env["DEBUG"]) {
console.log(`[DEBUG] Binary file detected: ${filePath}`);
}
return options?.skipHeader
? `[Binary file - content not displayed]`
: `================================\nFile: ${displayPath}\n================================\n[Binary file - content not displayed]`;
}
// If not binary, convert to string
const content = buffer.toString('utf8');
return options?.skipHeader
? content
: `================================\nFile: ${displayPath}\n================================\n${content}`;
}
catch (error) {
if (process.env["DEBUG"]) {
console.error(`[DEBUG] Error reading file ${filePath}:`, error);
}
return options?.skipHeader
? `[Error reading file: ${error instanceof Error ? error.message : String(error)}]`
: `================================\nFile: ${displayPath}\n================================\n[Error reading file: ${error instanceof Error ? error.message : String(error)}]`;
}
}
//# sourceMappingURL=fileUtils.js.map