import { simpleGit } from 'simple-git'; import { spawn } from 'child_process'; import path from 'path'; import fs from 'fs/promises'; import type { WorktreeInfo, Config, CLIOptions } from '../types/index.js'; const git = simpleGit(); export async function getProjectName(): Promise { try { // Try to get project name from GitHub origin remote const remotes = await git.getRemotes(true); const origin = remotes.find(remote => remote.name === 'origin'); if (origin?.refs?.fetch) { // Extract repo name from GitHub URL const match = origin.refs.fetch.match(/github\.com[:/]([^/]+)\/([^/.]+)/); if (match?.[2]) { return match[2]; } } } catch { // Fall back to current directory name if git operations fail } // Fallback to current directory name return path.basename(process.cwd()); } export async function setupWorktree( feature: string, options: CLIOptions & { config: Config } ): Promise { const projectName = options.project ?? options.config.projectName ?? await getProjectName(); const worktreeName = `${projectName}-${feature}`; const branchName = `feature/${feature}`; // Determine worktree location const baseLocation = options.location ?? options.config.worktreeLocation; const worktreePath = path.resolve(baseLocation, worktreeName); // Check if worktree already exists const worktreeExists = await checkWorktreeExists(worktreePath); const worktreeInfo: WorktreeInfo = { name: worktreeName, path: worktreePath, branch: branchName, exists: worktreeExists }; if (options.continue) { if (!worktreeExists) { throw new Error(`Worktree ${worktreeName} does not exist. Remove --continue flag to create it.`); } return worktreeInfo; } if (worktreeExists) { throw new Error(`Worktree ${worktreeName} already exists. Use --continue to mount it.`); } // Create new worktree console.log(`๐ŸŒฑ Creating worktree: ${worktreeName}`); console.log(`๐Ÿ“ Branch: ${branchName}`); console.log(`๐Ÿ“‚ Path: ${worktreePath}`); try { await git.raw(['worktree', 'add', '-b', branchName, worktreePath]); console.log('โœ… Worktree created successfully'); // Run setup script if configured if (options.config.onWorktreeCreate) { console.log(`๐Ÿ”ง Running setup script: ${options.config.onWorktreeCreate}`); await runSetupScript(options.config.onWorktreeCreate, worktreePath); } } catch (error) { throw new Error(`Failed to create worktree: ${error instanceof Error ? error.message : String(error)}`); } return { ...worktreeInfo, exists: true }; } async function checkWorktreeExists(path: string): Promise { try { const stats = await fs.stat(path); return stats.isDirectory(); } catch { return false; } } export async function cleanupWorktree(worktreePath: string): Promise { try { await git.raw(['worktree', 'remove', worktreePath]); console.log('๐Ÿงน Worktree cleaned up'); } catch (error) { console.warn(`Warning: Could not cleanup worktree: ${error instanceof Error ? error.message : String(error)}`); } } async function runSetupScript(script: string, worktreePath: string): Promise { return new Promise((resolve) => { const child = spawn(script, [], { cwd: worktreePath, shell: true, stdio: 'inherit' }); child.on('close', (code) => { if (code === 0) { console.log('โœ… Setup script completed successfully'); resolve(); } else { console.warn(`โš ๏ธ Setup script exited with code ${code}`); resolve(); // Don't fail the whole process if setup script fails } }); child.on('error', (error) => { console.warn(`โš ๏ธ Setup script failed: ${error.message}`); resolve(); // Don't fail the whole process if setup script fails }); }); }