UNPKG

aios-core

Version:

Synkra AIOS: AI-Orchestrated System for Full Stack Development - Core Framework

444 lines (376 loc) 9.73 kB
const { execa } = require('execa'); const path = require('path'); const fs = require('fs').promises; const chalk = require('chalk'); /** * GitWrapper - Centralized git operations for AIOS * * Refactored to use execa for cross-platform compatibility * All git operations route through execGit() method */ class GitWrapper { constructor(rootPath) { this.rootPath = rootPath || process.cwd(); this.gitPath = 'git'; } /** * Execute git command using execa * * @param {string} command - Git command with arguments as string * @param {object} options - Execution options * @returns {string} Command stdout output */ async execGit(command, options = {}) { try { // Split command string into array for execa const args = command.split(' '); const { stdout, stderr } = await execa(this.gitPath, args, { cwd: this.rootPath, ...options, }); if (stderr && !options.ignoreStderr) { console.warn(chalk.yellow(`Git warning: ${stderr}`)); } return stdout.trim(); } catch (error) { throw new Error(`Git command failed: ${error.message}`); } } /** * Check if current directory is a git repository */ async isGitInitialized() { try { await this.execGit('rev-parse --git-dir'); return true; } catch (error) { return false; } } /** * Initialize a new git repository */ async initializeRepository() { try { await this.execGit('init'); return true; } catch (error) { throw new Error(`Failed to initialize git repository: ${error.message}`); } } /** * Get current branch name */ async getCurrentBranch() { try { return await this.execGit('rev-parse --abbrev-ref HEAD'); } catch (error) { throw new Error(`Failed to get current branch: ${error.message}`); } } /** * Create a new branch */ async createBranch(branchName, baseBranch = null) { try { if (baseBranch) { await this.execGit(`checkout -b ${branchName} ${baseBranch}`); } else { await this.execGit(`checkout -b ${branchName}`); } return true; } catch (error) { throw new Error(`Failed to create branch ${branchName}: ${error.message}`); } } /** * Checkout existing branch */ async checkoutBranch(branchName) { try { await this.execGit(`checkout ${branchName}`); return true; } catch (error) { throw new Error(`Failed to checkout branch ${branchName}: ${error.message}`); } } /** * Create a modification branch with timestamp */ async createModificationBranch(modificationId) { const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); const branchName = `modification/${modificationId}/${timestamp}`; await this.createBranch(branchName); return branchName; } /** * Stage files for commit */ async stageFiles(files) { try { if (Array.isArray(files)) { for (const file of files) { await this.execGit(`add ${file}`); } } else { await this.execGit(`add ${files}`); } return true; } catch (error) { throw new Error(`Failed to stage files: ${error.message}`); } } /** * Create a commit */ async commit(message, options = {}) { try { const commitArgs = ['commit', '-m', message]; if (options.allowEmpty) { commitArgs.push('--allow-empty'); } // Note: execa handles arguments array directly const { stdout } = await execa(this.gitPath, commitArgs, { cwd: this.rootPath, }); return stdout.trim(); } catch (error) { throw new Error(`Failed to create commit: ${error.message}`); } } /** * Create a modification commit with metadata */ async commitModification(modificationId, description, metadata = {}) { const message = `[Modification ${modificationId}] ${description} Metadata: ${JSON.stringify(metadata, null, 2)}`; return await this.commit(message); } /** * Get repository status */ async getStatus() { try { const output = await this.execGit('status --porcelain'); const status = { clean: !output, modified: [], staged: [], untracked: [], }; if (!output) return status; const lines = output.split('\n').filter(line => line.trim()); for (const line of lines) { const statusCode = line.substring(0, 2); const file = line.substring(3); if (statusCode[0] === 'M') { status.staged.push(file); } else if (statusCode[1] === 'M') { status.modified.push(file); } else if (statusCode === '??') { status.untracked.push(file); } } return status; } catch (error) { throw new Error(`Failed to get status: ${error.message}`); } } /** * Get commit history */ async getHistory(limit = 10) { try { const output = await this.execGit(`log --oneline -n ${limit}`); return output.split('\n').map(line => { const [hash, ...messageParts] = line.split(' '); return { hash, message: messageParts.join(' '), }; }); } catch (error) { throw new Error(`Failed to get history: ${error.message}`); } } /** * Check for merge conflicts */ async getConflicts() { try { const output = await this.execGit('diff --name-only --diff-filter=U'); return output ? output.split('\n').filter(f => f.trim()) : []; } catch (error) { return []; } } /** * Merge a branch */ async mergeBranch(branchName, options = {}) { try { const mergeArgs = ['merge', branchName]; if (options.noFf) { mergeArgs.push('--no-ff'); } if (options.message) { mergeArgs.push('-m', options.message); } const { stdout } = await execa(this.gitPath, mergeArgs, { cwd: this.rootPath, }); return { success: true, output: stdout.trim(), }; } catch (error) { const conflicts = await this.getConflicts(); return { success: false, conflicts, error: error.message, }; } } /** * Create a git tag */ async createTag(tagName, message = null) { const tagArgs = message ? `tag -a ${tagName} -m "${message}"` : `tag ${tagName}`; return await this.execGit(tagArgs); } /** * Push to remote */ async push(remote = 'origin', branch = null, options = {}) { try { const pushArgs = ['push', remote]; if (branch) { pushArgs.push(branch); } if (options.setUpstream) { pushArgs.push('--set-upstream'); } if (options.force) { pushArgs.push('--force'); } const { stdout } = await execa(this.gitPath, pushArgs, { cwd: this.rootPath, }); return stdout.trim(); } catch (error) { throw new Error(`Failed to push: ${error.message}`); } } /** * Get diff for files */ async getDiff(files = null, options = {}) { try { let diffCommand = 'diff'; if (options.staged) { diffCommand += ' --staged'; } if (options.nameOnly) { diffCommand += ' --name-only'; } if (files) { if (Array.isArray(files)) { diffCommand += ' ' + files.join(' '); } else { diffCommand += ' ' + files; } } return await this.execGit(diffCommand); } catch (error) { return ''; } } /** * Stash changes */ async stash(message = null) { const stashCommand = message ? `stash save "${message}"` : 'stash'; return await this.execGit(stashCommand); } /** * Apply stashed changes */ async stashApply(stashRef = null) { const applyCommand = stashRef ? `stash apply ${stashRef}` : 'stash apply'; return await this.execGit(applyCommand); } /** * Get remote repositories */ async getRemotes() { try { const output = await this.execGit('remote -v'); const remotes = {}; if (!output) return remotes; const lines = output.split('\n').filter(line => line.trim()); for (const line of lines) { const [name, url, type] = line.split(/\s+/); if (!remotes[name]) { remotes[name] = { fetch: null, push: null }; } remotes[name][type.replace(/[()]/g, '')] = url; } return remotes; } catch (error) { return {}; } } /** * Add remote repository */ async addRemote(name, url) { return await this.execGit(`remote add ${name} ${url}`); } /** * Remove remote repository */ async removeRemote(name) { return await this.execGit(`remote remove ${name}`); } /** * Fetch from remote */ async fetch(remote = 'origin', options = {}) { const fetchArgs = ['fetch', remote]; if (options.prune) { fetchArgs.push('--prune'); } if (options.all) { fetchArgs.push('--all'); } const { stdout } = await execa(this.gitPath, fetchArgs, { cwd: this.rootPath, }); return stdout.trim(); } /** * Pull from remote */ async pull(remote = 'origin', branch = null, options = {}) { const pullArgs = ['pull', remote]; if (branch) { pullArgs.push(branch); } if (options.rebase) { pullArgs.push('--rebase'); } const { stdout } = await execa(this.gitPath, pullArgs, { cwd: this.rootPath, }); return stdout.trim(); } } module.exports = GitWrapper;