diff --git a/scripts/move-env-to-worktree.ts b/scripts/move-env-to-worktree.ts index 11ecd162f..f73fee82f 100755 --- a/scripts/move-env-to-worktree.ts +++ b/scripts/move-env-to-worktree.ts @@ -1,22 +1,128 @@ #!/usr/bin/env tsx import { promises as fs } from 'node:fs'; -import { join, relative, dirname } from 'node:path'; -import { glob } from 'glob'; +import { join, relative, dirname, basename } from 'node:path'; const SOURCE_REPO = join(process.env.HOME!, 'buster', 'buster'); const TARGET_REPO = process.cwd(); +interface GitignoreRules { + patterns: string[]; + directory: string; +} + +// Simple gitignore pattern matcher +function matchesGitignorePattern(path: string, pattern: string): boolean { + // Remove leading/trailing slashes + pattern = pattern.trim(); + if (pattern.startsWith('#') || pattern === '') return false; + + const isNegation = pattern.startsWith('!'); + if (isNegation) pattern = pattern.slice(1); + + // Handle directory-only patterns (ending with /) + const isDirPattern = pattern.endsWith('/'); + if (isDirPattern) pattern = pattern.slice(0, -1); + + // Simple glob matching (basic implementation) + // Convert pattern to regex + let regexPattern = pattern + .replace(/\./g, '\\.') + .replace(/\*/g, '[^/]*') + .replace(/\?/g, '[^/]') + .replace(/\*\*/g, '.*'); + + // If pattern doesn't start with /, it can match anywhere + if (!pattern.startsWith('/')) { + regexPattern = `(^|/)${regexPattern}`; + } else { + regexPattern = `^${regexPattern.slice(1)}`; + } + + if (isDirPattern) { + regexPattern += '(/|$)'; + } else { + regexPattern += '(/|$)'; + } + + const regex = new RegExp(regexPattern); + const matches = regex.test(path); + + return isNegation ? !matches : matches; +} + +async function loadGitignoreRules(dir: string): Promise { + try { + const gitignorePath = join(dir, '.gitignore'); + const content = await fs.readFile(gitignorePath, 'utf-8'); + return content + .split('\n') + .map(line => line.trim()) + .filter(line => line && !line.startsWith('#')); + } catch { + return []; + } +} + +function isIgnored(path: string, gitignoreStack: GitignoreRules[]): boolean { + for (const rules of gitignoreStack) { + const relativePath = relative(rules.directory, path); + if (relativePath && !relativePath.startsWith('..')) { + for (const pattern of rules.patterns) { + if (matchesGitignorePattern(relativePath, pattern)) { + return true; + } + } + } + } + return false; +} + +async function findEnvFiles(dir: string, baseDir: string = dir): Promise { + const envFiles: string[] = []; + + async function walkDir(currentDir: string, gitignoreStack: GitignoreRules[]) { + try { + // Load gitignore rules for current directory + const localRules = await loadGitignoreRules(currentDir); + if (localRules.length > 0) { + gitignoreStack = [...gitignoreStack, { patterns: localRules, directory: currentDir }]; + } + + const entries = await fs.readdir(currentDir, { withFileTypes: true }); + + for (const entry of entries) { + const fullPath = join(currentDir, entry.name); + const relativePath = relative(baseDir, fullPath); + + // Check if this path is ignored + if (isIgnored(fullPath, gitignoreStack)) { + continue; + } + + if (entry.isDirectory()) { + // Always skip .git directory + if (entry.name === '.git') continue; + await walkDir(fullPath, gitignoreStack); + } else if (entry.name.startsWith('.env')) { + envFiles.push(relativePath); + } + } + } catch (error) { + // Skip directories we can't read + console.warn(`Skipping directory: ${currentDir}`); + } + } + + await walkDir(dir, []); + return envFiles; +} + async function copyEnvFiles() { try { console.info(`Searching for .env files in ${SOURCE_REPO}...`); - const envFiles = await glob('**/.env*', { - cwd: SOURCE_REPO, - absolute: false, - dot: true, - ignore: ['**/node_modules/**', '**/.git/**'] - }); + const envFiles = await findEnvFiles(SOURCE_REPO); if (envFiles.length === 0) { console.warn('No .env files found in source repository');