UNPKG

autoagent-cli

Version:

Run autonomous AI agents using Claude or Gemini for task execution

453 lines (452 loc) 16 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.checkGitAvailable = checkGitAvailable; exports.getGitVersion = getGitVersion; exports.isGitRepository = isGitRepository; exports.getGitStatus = getGitStatus; exports.stageAllChanges = stageAllChanges; exports.createCommit = createCommit; exports.getCurrentCommitHash = getCurrentCommitHash; exports.getUncommittedChanges = getUncommittedChanges; exports.hasChangesToCommit = hasChangesToCommit; exports.revertToCommit = revertToCommit; exports.getChangedFiles = getChangedFiles; exports.validateGitEnvironment = validateGitEnvironment; exports.getCurrentBranch = getCurrentBranch; exports.hasUpstreamBranch = hasUpstreamBranch; exports.checkGitRemote = checkGitRemote; exports.pushToRemote = pushToRemote; exports.validateRemoteForPush = validateRemoteForPush; const child_process_1 = require("child_process"); const util_1 = require("util"); const execAsync = (0, util_1.promisify)(child_process_1.exec); async function checkGitAvailable() { try { await execAsync('git --version'); return true; } catch { return false; } } async function getGitVersion() { try { const { stdout } = await execAsync('git --version'); return stdout.trim(); } catch { return null; } } async function isGitRepository() { try { await execAsync('git rev-parse --git-dir'); return true; } catch { return false; } } async function getGitStatus() { const status = { isRepo: false, isDirty: false, hasUncommitted: false, hasUntracked: false, branch: '', ahead: 0, behind: 0 }; try { status.isRepo = await isGitRepository(); if (!status.isRepo) { return status; } const { stdout: branch } = await execAsync('git rev-parse --abbrev-ref HEAD'); status.branch = branch.trim(); const { stdout: diffStatus } = await execAsync('git diff --stat'); status.hasUncommitted = diffStatus.trim() !== ''; const { stdout: untrackedFiles } = await execAsync('git ls-files --others --exclude-standard'); status.hasUntracked = untrackedFiles.trim() !== ''; try { await execAsync('git diff-index --quiet HEAD --'); status.isDirty = false; } catch { status.isDirty = true; } try { const { stdout: revList } = await execAsync('git rev-list --left-right --count HEAD...@{upstream}'); const [ahead, behind] = revList.trim().split('\t').map(n => parseInt(n, 10)); status.ahead = ahead ?? 0; status.behind = behind ?? 0; } catch { } return status; } catch (error) { return status; } } async function stageAllChanges() { await execAsync('git add -A'); } async function createCommit(options) { try { const { message, coAuthor, signoff, noVerify } = options; let fullMessage = message; if (coAuthor) { fullMessage += `\n\nCo-authored-by: ${coAuthor.name} <${coAuthor.email}>`; } let command = `git commit -m "${fullMessage.replace(/"/g, '\\"')}"`; if (signoff === true) { command += ' --signoff'; } if (noVerify === true) { command += ' --no-verify'; } const { stdout } = await execAsync(command); const hashMatch = stdout.match(/\[[\w\s-]+\s+([a-f0-9]+)\]/); const commitHash = hashMatch ? hashMatch[1] : undefined; return { success: true, commitHash }; } catch (error) { return { success: false, error: error instanceof Error ? error.message : String(error) }; } } async function getCurrentCommitHash() { try { const { stdout } = await execAsync('git rev-parse HEAD'); return stdout.trim(); } catch { return null; } } async function getUncommittedChanges() { try { const { stdout } = await execAsync('git diff HEAD'); return stdout; } catch { return ''; } } async function hasChangesToCommit() { try { const { stdout: staged } = await execAsync('git diff --cached --stat'); if (staged.trim() !== '') { return true; } const { stdout: unstaged } = await execAsync('git diff --stat'); if (unstaged.trim() !== '') { return true; } const { stdout: untracked } = await execAsync('git ls-files --others --exclude-standard'); return untracked.trim() !== ''; } catch { return false; } } async function revertToCommit(commitHash) { try { await execAsync(`git reset --hard ${commitHash}`); return true; } catch (error) { return false; } } async function getChangedFiles() { try { const files = []; const { stdout: staged } = await execAsync('git diff --cached --name-only'); if (staged.trim() !== '') { files.push(...staged.trim().split('\n')); } const { stdout: unstaged } = await execAsync('git diff --name-only'); if (unstaged.trim() !== '') { files.push(...unstaged.trim().split('\n')); } const { stdout: untracked } = await execAsync('git ls-files --others --exclude-standard'); if (untracked.trim() !== '') { files.push(...untracked.trim().split('\n')); } return Array.from(new Set(files)); } catch { return []; } } async function validateGitEnvironment(options = {}) { const errors = []; const suggestions = []; const debug = options.onDebug || (() => { }); let gitVersion; debug('🔍 Starting git validation...'); debug('📋 Checking git availability...'); const gitAvailable = await checkGitAvailable(); if (!gitAvailable) { debug('❌ Git is not available'); errors.push('Git is not installed or not accessible in PATH'); suggestions.push('Install Git from https://git-scm.com/downloads'); suggestions.push('Ensure git is added to your system PATH'); } else { const version = await getGitVersion(); gitVersion = version ?? undefined; debug(`✅ Git is available: ${version ?? 'unknown version'}`); } debug('📁 Checking repository status...'); const isRepo = await isGitRepository(); if (!isRepo) { debug('❌ Not a git repository'); errors.push('Current directory is not a Git repository'); suggestions.push('Initialize a new repository with: git init'); suggestions.push('Or clone an existing repository with: git clone <url>'); } else { debug('✅ Valid git repository detected'); } if (gitAvailable && isRepo) { debug('👤 Checking user configuration...'); try { const { stdout: userName } = await execAsync('git config user.name'); if (!userName.trim()) { debug('❌ Git user name is not configured'); errors.push('Git user name is not configured'); suggestions.push('Set your name with: git config --global user.name "Your Name"'); } else { debug(`✅ Git user name: ${userName.trim()}`); } } catch { debug('❌ Failed to check git user name'); errors.push('Git user name is not configured'); suggestions.push('Set your name with: git config --global user.name "Your Name"'); } try { const { stdout: userEmail } = await execAsync('git config user.email'); if (!userEmail.trim()) { debug('❌ Git user email is not configured'); errors.push('Git user email is not configured'); suggestions.push('Set your email with: git config --global user.email "your.email@example.com"'); } else { debug(`✅ Git user email: ${userEmail.trim()}`); } } catch { debug('❌ Failed to check git user email'); errors.push('Git user email is not configured'); suggestions.push('Set your email with: git config --global user.email "your.email@example.com"'); } debug('🌐 Checking remote repository...'); try { const { stdout: remotes } = await execAsync('git remote -v'); if (!remotes.trim()) { debug('⚠️ No remote repository configured (warning)'); errors.push('No remote repository configured'); suggestions.push('Add a remote with: git remote add origin <repository-url>'); suggestions.push('View existing remotes with: git remote -v'); } else { debug(`✅ Remote repository configured:\n${remotes.trim()}`); } } catch { debug('❌ Unable to check remote repositories'); errors.push('Unable to check remote repositories'); suggestions.push('Check git configuration with: git remote -v'); } } const isValid = errors.length === 0; if (isValid) { debug('✅ Git validation completed successfully'); } else { debug(`❌ Git validation failed with ${errors.length} error(s)`); errors.forEach((error, index) => { debug(` ${index + 1}. ${error}`); }); } return { isValid, errors, suggestions, gitVersion }; } async function getCurrentBranch() { try { const { stdout } = await execAsync('git rev-parse --abbrev-ref HEAD'); const branch = stdout.trim(); if (branch === 'HEAD') { return null; } return branch; } catch { return null; } } async function hasUpstreamBranch() { try { await execAsync('git rev-parse --abbrev-ref --symbolic-full-name @{upstream}'); return true; } catch { return false; } } async function checkGitRemote(remote = 'origin') { try { const { stdout: remotes } = await execAsync('git remote'); const remotesList = remotes.trim().split('\n').filter(r => r); if (!remotesList.includes(remote)) { return { exists: false, accessible: false, error: `Remote '${remote}' does not exist` }; } try { await execAsync(`git ls-remote --exit-code ${remote} HEAD`, { timeout: 30000 }); return { exists: true, accessible: true }; } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); if (errorMessage.includes('Authentication failed') || errorMessage.includes('Permission denied') || errorMessage.includes('Could not read from remote repository')) { return { exists: true, accessible: false, error: 'Authentication failed. Please check your credentials' }; } if (errorMessage.includes('Could not resolve host') || errorMessage.includes('unable to access') || errorMessage.includes('Network is unreachable')) { return { exists: true, accessible: false, error: 'Network error. Please check your internet connection' }; } return { exists: true, accessible: false, error: `Remote is not accessible: ${errorMessage}` }; } } catch (error) { return { exists: false, accessible: false, error: error instanceof Error ? error.message : String(error) }; } } async function pushToRemote(options = {}) { try { const { remote = 'origin', branch, force = false, setUpstream = false } = options; const targetBranch = branch ?? await getCurrentBranch(); if (targetBranch === null) { return { success: false, error: 'Cannot push from detached HEAD state. Please checkout a branch first' }; } let command = 'git push'; if (force) { command += ' --force-with-lease'; } if (setUpstream) { command += ' --set-upstream'; } command += ` ${remote} ${targetBranch}`; const { stderr } = await execAsync(command); return { success: true, remote, branch: targetBranch, stderr: stderr || undefined }; } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); let errorStderr; if (error !== null && typeof error === 'object' && 'stderr' in error && typeof error.stderr === 'string') { errorStderr = error.stderr; } return { success: false, error: errorMessage, stderr: errorStderr }; } } async function validateRemoteForPush(remote = 'origin') { const errors = []; const suggestions = []; const isRepo = await isGitRepository(); if (!isRepo) { errors.push('Not in a git repository'); suggestions.push('Initialize a repository with: git init'); suggestions.push('Or clone an existing repository with: git clone <url>'); return { isValid: false, errors, suggestions }; } const currentBranch = await getCurrentBranch(); if (currentBranch === null) { errors.push('Currently in detached HEAD state'); suggestions.push('Checkout a branch with: git checkout -b <branch-name>'); suggestions.push('Or checkout an existing branch with: git checkout <branch-name>'); return { isValid: false, errors, suggestions }; } const remoteStatus = await checkGitRemote(remote); if (!remoteStatus.exists) { errors.push(`Remote '${remote}' does not exist`); suggestions.push(`Add a remote with: git remote add ${remote} <repository-url>`); suggestions.push('List existing remotes with: git remote -v'); } else if (!remoteStatus.accessible) { const errorMessage = remoteStatus.error ?? 'Unknown error'; errors.push(`Remote '${remote}' is not accessible: ${errorMessage}`); if (remoteStatus.error !== undefined && remoteStatus.error.includes('Authentication')) { suggestions.push('Check your git credentials are configured correctly'); suggestions.push('For HTTPS: Update credentials in your credential manager'); suggestions.push('For SSH: Ensure your SSH key is added to the remote repository'); } else if (remoteStatus.error !== undefined && remoteStatus.error.includes('Network')) { suggestions.push('Check your internet connection'); suggestions.push('Verify the remote URL is correct with: git remote -v'); } else { suggestions.push('Verify the remote URL with: git remote -v'); suggestions.push(`Test remote connectivity with: git ls-remote ${remote}`); } } const hasUpstream = await hasUpstreamBranch(); if (!hasUpstream && remoteStatus.exists && remoteStatus.accessible) { suggestions.push(`Set upstream tracking with: git push --set-upstream ${remote} ${currentBranch}`); } return { isValid: errors.length === 0, errors, suggestions }; }