ignore-manager.js•6.97 kB
import ignore from 'ignore';
import { existsSync, readFileSync, readdirSync } from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
const ignoreFilterCache = new Map();
const CACHE_TTL = 5 * 60 * 1000;
// Get the directory of the current module
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
// Load universal ignore patterns from the shared file
function loadUniversalIgnorePatterns() {
  const universalIgnorePath = path.join(__dirname, 'universal-ignore.txt');
  try {
    const content = readFileSync(universalIgnorePath, 'utf8');
    return content
      .split('\n')
      .filter(line => line.trim() && !line.startsWith('#'))
      .map(line => line.trim());
  } catch (error) {
    console.warn(`Warning: Could not load universal ignore patterns from ${universalIgnorePath}: ${error.message}`);
    // Fallback to minimal patterns if file can't be read
    return [
      'node_modules/**',
      '.git/**',
      'build/**',
      'dist/**',
      'coverage/**',
      '*.log',
      '.DS_Store',
      'Thumbs.db'
    ];
  }
}
export const CORE_IGNORE_PATTERNS = loadUniversalIgnorePatterns();
export const SOURCE_CODE_EXTENSIONS = [
  // Core web development
  '.js', '.jsx', '.ts', '.tsx', '.mjs', '.cjs',
  // Systems programming
  '.go', '.rs', '.py', '.pyx',
  '.c', '.cpp', '.cc', '.cxx', '.h', '.hpp',
  // JVM languages
  '.java', '.kt', '.scala', '.groovy',
  // Mobile and Swift
  '.swift', '.m', '.mm', '.dart',
  // Shell and scripting
  '.sh', '.bash', '.zsh', '.fish', '.ps1',
  '.pl', '.pm', '.t', '.pod', '.lua',
  // Data and configuration
  '.r', '.R', '.Rmd', '.sql',
  '.json', '.yaml', '.yml', '.toml', '.ini', '.conf',
  // Web markup and styling (processed selectively)
  '.html', '.htm', '.css', '.scss', '.sass', '.less',
  '.vue', '.svelte', '.astro',
  // Documentation (limited processing)
  '.md', '.markdown', '.txt'
  // Removed redundant extensions and non-essential formats
  // for better performance and focus on source code
];
export function createIgnoreFilter(rootDir, customPatterns = [], options = {}) {
  const {
    useGitignore = true,
    useDefaults = true,
    caseSensitive = false,
    useCache = true
  } = options;
  
  const cacheKey = JSON.stringify({
    rootDir: path.resolve(rootDir),
    customPatterns: customPatterns.sort(),
    useGitignore,
    useDefaults,
    caseSensitive
  });
  
  if (useCache) {
    const cached = ignoreFilterCache.get(cacheKey);
    if (cached && (Date.now() - cached.timestamp) < CACHE_TTL) {
      return cached.filter;
    }
  }
  const ig = ignore({ caseSensitive });
  
  if (useDefaults) {
    ig.add(CORE_IGNORE_PATTERNS);
  }
  
  if (customPatterns.length > 0) {
    ig.add(customPatterns);
  }
  
  if (useGitignore) {
    addGitignoreFiles(ig, rootDir);
  }
  const filter = {
    ig,
    rootDir,
    ignores: (filePath) => {
      // Ensure both paths are absolute for path.relative()
      const absoluteRoot = path.resolve(rootDir);
      const absoluteFile = path.resolve(filePath);
      const relativePath = path.relative(absoluteRoot, absoluteFile).replace(/\\/g, '/');
      return ig.ignores(relativePath);
    },
    add: (patterns) => ig.add(patterns),
    createSubFilter: (subDir) => createIgnoreFilter(subDir, customPatterns, options)
  };
  
  if (useCache) {
    ignoreFilterCache.set(cacheKey, {
      filter,
      timestamp: Date.now()
    });
  }
  return filter;
}
function addGitignoreFiles(ig, rootDir) {
  const scannedDirs = new Set();
  const MAX_DEPTH = 10;
  const MAX_DIRS = 1000;
  const scanGitignoreFiles = (dir, depth = 0) => {
    
    if (depth > MAX_DEPTH) return;
    if (scannedDirs.size > MAX_DIRS) return;
    const dirKey = path.resolve(dir);
    if (scannedDirs.has(dirKey)) return;
    scannedDirs.add(dirKey);
    try {
      const entries = readdirSync(dir, { withFileTypes: true });
      
      const gitignorePath = path.join(dir, '.gitignore');
      if (existsSync(gitignorePath)) {
        try {
          const content = readFileSync(gitignorePath, 'utf8');
          const patterns = content
            .split('\n')
            .filter(line => line.trim() && !line.startsWith('#'))
            .map(line => line.trim());
          if (patterns.length > 0) {
            ig.add(patterns);
          }
        } catch (error) {
          
          console.warn(`Warning: Could not read .gitignore at ${gitignorePath}: ${error.message}`);
        }
      }
      
      for (const entry of entries) {
        if (entry.isDirectory()) {
          const fullPath = path.join(dir, entry.name);
          
          if (entry.name.startsWith('.') ||
              entry.name === 'node_modules' ||
              entry.name === 'dist' ||
              entry.name === 'build' ||
              entry.name === 'target' ||
              entry.name === 'vendor' ||
              entry.name === 'coverage' ||
              entry.name === '.git') {
            continue;
          }
          scanGitignoreFiles(fullPath, depth + 1);
        }
      }
    } catch (error) {
      
      if (error.code !== 'ENOENT') {
        console.warn(`Warning: Could not scan directory ${dir}: ${error.message}`);
      }
    }
  };
  scanGitignoreFiles(rootDir);
}
export function createExtensionFilter(extensions = SOURCE_CODE_EXTENSIONS) {
  return (filePath) => {
    const ext = path.extname(filePath).toLowerCase();
    return extensions.includes(ext);
  };
}
export function createFileFilter(rootDir, customPatterns = [], options = {}) {
  const {
    extensions = SOURCE_CODE_EXTENSIONS,
    ...ignoreOptions
  } = options;
  const ignoreFilter = createIgnoreFilter(rootDir, customPatterns, ignoreOptions);
  const extensionFilter = createExtensionFilter(extensions);
  const fileFilter = {
    ...ignoreFilter,
    shouldProcess: (filePath) => {
      return !ignoreFilter.ignores(filePath) && extensionFilter(filePath);
    },
    filterFiles: (files) => files.filter(file => fileFilter.shouldProcess(file))
  };
  return fileFilter;
}
export function getDefaultIgnorePatterns() {
  return CORE_IGNORE_PATTERNS;
}
export function shouldIgnoreFile(filePath, ignorePatterns = null) {
  const patterns = ignorePatterns || CORE_IGNORE_PATTERNS;
  const ignoreFilter = createIgnoreFilter(path.dirname(filePath), patterns);
  return ignoreFilter.ignores(filePath);
}
export function clearIgnoreCache() {
  ignoreFilterCache.clear();
}
export function reloadUniversalIgnorePatterns() {
  // Clear cache to force reload with new patterns
  clearIgnoreCache();
  // Reload patterns from file
  return loadUniversalIgnorePatterns();
}
export function getCacheStats() {
  return {
    size: ignoreFilterCache.size,
    entries: Array.from(ignoreFilterCache.entries())
  };
}
export default {
  CORE_IGNORE_PATTERNS,
  SOURCE_CODE_EXTENSIONS,
  createIgnoreFilter,
  createExtensionFilter,
  createFileFilter,
  getDefaultIgnorePatterns,
  shouldIgnoreFile
};