import { existsSync, readFileSync } from 'fs' import { join } from 'path' const ALWAYS_EXCLUDE = [ 'node_modules', '.DS_Store', '.git', '*~', ] const DEFAULT_PATTERNS = [ ...ALWAYS_EXCLUDE, '*.log', 'dist', 'build', '.env.local', 'bun.lockb', ] export interface GitignoreChecker { shouldExclude: (path: string) => boolean } export function loadGitignore(appPath: string): GitignoreChecker { const gitignorePath = join(appPath, '.gitignore') const patterns = existsSync(gitignorePath) ? [...ALWAYS_EXCLUDE, ...parseGitignore(readFileSync(gitignorePath, 'utf-8'))] : DEFAULT_PATTERNS return { shouldExclude: (path: string) => matchesPattern(path, patterns), } } function parseGitignore(content: string): string[] { return content .split('\n') .map(line => line.trim()) .filter(line => line && !line.startsWith('#')) } function matchesPattern(path: string, patterns: string[]): boolean { const parts = path.split('/') const filename = parts[parts.length - 1]! for (const pattern of patterns) { // Patterns with wildcards or globs if (pattern.includes('*') || pattern.includes('?')) { const regex = globToRegex(pattern) // Try matching against full path and just filename if (regex.test(path) || regex.test(filename)) return true continue } // Directory match: "node_modules" or "node_modules/" const dirPattern = pattern.endsWith('/') ? pattern.slice(0, -1) : pattern if (parts.includes(dirPattern)) return true // Prefix patterns: "dist/" or "build/" if (pattern.endsWith('/') && path.startsWith(pattern)) return true // Exact match if (path === pattern || filename === pattern) return true } return false } function globToRegex(glob: string): RegExp { // Handle ** specially - it can match zero or more path segments // Use placeholders to avoid conflicts with regex escaping let pattern = glob .replace(/\*\*\//g, '\x00') // **/ placeholder .replace(/\*\*/g, '\x01') // ** placeholder .replace(/\./g, '\\.') // Escape dots .replace(/\*/g, '[^/]*') // Single * matches within path segment .replace(/\?/g, '[^/]') // ? matches single char .replace(/\x00/g, '(.*/)?') // **/ matches zero or more dirs .replace(/\x01/g, '.*') // ** matches anything return new RegExp(`^${pattern}$`) }