/** * Helper for creating test git repositories for integration tests */ import { exec } from "child_process"; import { promisify } from "util"; import * as fs from "fs/promises"; import * as path from "path"; import * as os from "os"; const execAsync = promisify(exec); export interface TestRepo { path: string; cleanup: () => Promise; } /** * Create a temporary git repository for testing */ export async function createTestGitRepo(): Promise { const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "automaker-test-")); // Initialize git repo await execAsync("git init", { cwd: tmpDir }); await execAsync('git config user.email "test@example.com"', { cwd: tmpDir }); await execAsync('git config user.name "Test User"', { cwd: tmpDir }); // Create initial commit await fs.writeFile(path.join(tmpDir, "README.md"), "# Test Project\n"); await execAsync("git add .", { cwd: tmpDir }); await execAsync('git commit -m "Initial commit"', { cwd: tmpDir }); // Create main branch explicitly await execAsync("git branch -M main", { cwd: tmpDir }); return { path: tmpDir, cleanup: async () => { try { // Remove all worktrees first const { stdout } = await execAsync("git worktree list --porcelain", { cwd: tmpDir, }).catch(() => ({ stdout: "" })); const worktrees = stdout .split("\n\n") .slice(1) // Skip main worktree .map((block) => { const pathLine = block.split("\n").find((line) => line.startsWith("worktree ")); return pathLine ? pathLine.replace("worktree ", "") : null; }) .filter(Boolean); for (const worktreePath of worktrees) { try { await execAsync(`git worktree remove "${worktreePath}" --force`, { cwd: tmpDir, }); } catch (err) { // Ignore errors } } // Remove the repository await fs.rm(tmpDir, { recursive: true, force: true }); } catch (error) { console.error("Failed to cleanup test repo:", error); } }, }; } /** * Create a feature file in the test repo */ export async function createTestFeature( repoPath: string, featureId: string, featureData: any ): Promise { const featuresDir = path.join(repoPath, ".automaker", "features"); const featureDir = path.join(featuresDir, featureId); await fs.mkdir(featureDir, { recursive: true }); await fs.writeFile( path.join(featureDir, "feature.json"), JSON.stringify(featureData, null, 2) ); } /** * Get list of git branches */ export async function listBranches(repoPath: string): Promise { const { stdout } = await execAsync("git branch --list", { cwd: repoPath }); return stdout .split("\n") .map((line) => line.trim().replace(/^[*+]\s*/, "")) .filter(Boolean); } /** * Get list of git worktrees */ export async function listWorktrees(repoPath: string): Promise { try { const { stdout } = await execAsync("git worktree list --porcelain", { cwd: repoPath, }); return stdout .split("\n\n") .slice(1) // Skip main worktree .map((block) => { const pathLine = block.split("\n").find((line) => line.startsWith("worktree ")); return pathLine ? pathLine.replace("worktree ", "") : null; }) .filter(Boolean) as string[]; } catch { return []; } } /** * Check if a branch exists */ export async function branchExists( repoPath: string, branchName: string ): Promise { const branches = await listBranches(repoPath); return branches.includes(branchName); } /** * Check if a worktree exists */ export async function worktreeExists( repoPath: string, worktreePath: string ): Promise { const worktrees = await listWorktrees(repoPath); return worktrees.some((wt) => wt === worktreePath); }