Skip to main content
Glama
githubAdapter.ts6.42 kB
import fs from 'fs'; import path from 'path'; import { execSync } from 'child_process'; import { Repository } from '../core/repository'; import { SavedRepositoryConfig } from '../core/repositoryManager'; export class GitHubAdapter { private baseClonePath: string; constructor(baseClonePath: string = '.lspace_clones') { this.baseClonePath = path.resolve(process.cwd(), baseClonePath); if (!fs.existsSync(this.baseClonePath)) { fs.mkdirSync(this.baseClonePath, { recursive: true }); } } private getLocalPath(owner: string, repo: string): string { return path.join(this.baseClonePath, owner, repo); } async initialize(repoConfig: SavedRepositoryConfig, pat: string): Promise<Repository> { if (repoConfig.type !== 'github' || !repoConfig.owner || !repoConfig.repo) { throw new Error('Invalid repository configuration for GitHubAdapter. Missing owner or repo.'); } const localPath = this.getLocalPath(repoConfig.owner, repoConfig.repo); const authenticatedUrl = `https://x-access-token:${pat}@github.com/${repoConfig.owner}/${repoConfig.repo}.git`; const cloneTargetParentDir = path.dirname(localPath); try { if (fs.existsSync(path.join(localPath, '.git'))) { console.log(`GitHubAdapter: Repository ${repoConfig.owner}/${repoConfig.repo} already cloned at ${localPath}. Configuring remote and fetching latest changes...`); execSync(`git -C "${localPath}" remote set-url origin "${authenticatedUrl}"`, { stdio: 'pipe' }); execSync(`git -C "${localPath}" fetch origin ${repoConfig.branch || 'main'} --depth=1 --tags`, { stdio: 'pipe' }); execSync(`git -C "${localPath}" checkout ${repoConfig.branch || 'main'} --force`, { stdio: 'pipe' }); console.log(`GitHubAdapter: Fetched and checked out ${repoConfig.branch || 'main'} for ${repoConfig.owner}/${repoConfig.repo}.`); } else { console.log(`GitHubAdapter: Cloning repository ${repoConfig.owner}/${repoConfig.repo} to ${localPath}...`); if (!fs.existsSync(cloneTargetParentDir)) { fs.mkdirSync(cloneTargetParentDir, { recursive: true }); } execSync(`git clone --branch ${repoConfig.branch || 'main'} --depth 1 "${authenticatedUrl}" "${localPath}"`, { stdio: 'pipe' }); console.log(`GitHubAdapter: Cloned ${repoConfig.owner}/${repoConfig.repo} successfully.`); } return new Repository(localPath); } catch (error: any) { console.error(`GitHubAdapter: Failed to initialize repository ${repoConfig.owner}/${repoConfig.repo}: ${error.message}`); if (error.stderr) { console.error(`GitHubAdapter: Git stderr: ${error.stderr.toString()}`); } if (error.stdout) { console.error(`GitHubAdapter: Git stdout: ${error.stdout.toString()}`); } throw error; } } async push(repoConfig: SavedRepositoryConfig, pat: string): Promise<void> { if (repoConfig.type !== 'github' || !repoConfig.owner || !repoConfig.repo) { throw new Error(`Cannot push non-GitHub repository: ${repoConfig.name}. Missing owner or repo.`); } const localPath = this.getLocalPath(repoConfig.owner, repoConfig.repo); const authenticatedUrl = `https://x-access-token:${pat}@github.com/${repoConfig.owner}/${repoConfig.repo}.git`; const branchToPush = repoConfig.branch || 'main'; // Default to main if not specified try { console.log(`GitHubAdapter: Pushing changes in ${localPath} to ${repoConfig.owner}/${repoConfig.repo} branch ${branchToPush} using Git CLI...`); // Standard push. Add --force or --force-with-lease if needed based on desired behavior. execSync(`git -C "${localPath}" push "${authenticatedUrl}" ${branchToPush}`, { stdio: 'pipe' }); console.log(`GitHubAdapter: Successfully pushed to ${repoConfig.owner}/${repoConfig.repo} branch ${branchToPush}.`); } catch (error: any) { console.error(`GitHubAdapter: Failed to push to ${repoConfig.owner}/${repoConfig.repo} branch ${branchToPush}: ${error.message}`); if (error.stderr) { console.error(`GitHubAdapter: Git stderr: ${error.stderr.toString()}`); } if (error.stdout) { console.error(`GitHubAdapter: Git stdout: ${error.stdout.toString()}`); } // Consider specific error handling here if a force push fallback is still desired for certain errors. throw error; } } async sync(repoConfig: SavedRepositoryConfig, pat: string): Promise<void> { if (repoConfig.type !== 'github' || !repoConfig.owner || !repoConfig.repo) { console.log(`GitHubAdapter: Repository ${repoConfig.name} is not a GitHub repository or is missing owner/repo. Skipping sync.`); return; } const localPath = this.getLocalPath(repoConfig.owner, repoConfig.repo); const authenticatedUrl = `https://x-access-token:${pat}@github.com/${repoConfig.owner}/${repoConfig.repo}.git`; const branchToSync = repoConfig.branch || 'main'; try { console.log(`GitHubAdapter: Syncing ${localPath} with ${repoConfig.owner}/${repoConfig.repo} branch ${branchToSync} using Git CLI...`); if (!fs.existsSync(path.join(localPath, '.git'))) { console.log(`GitHubAdapter: Local repository not found at ${localPath}. Cannot sync. Consider initializing first.`); // Optionally, call initialize or throw an error return; } execSync(`git -C "${localPath}" remote set-url origin "${authenticatedUrl}"`, { stdio: 'pipe' }); execSync(`git -C "${localPath}" fetch origin ${branchToSync} --depth=1 --tags`, { stdio: 'pipe' }); execSync(`git -C "${localPath}" checkout ${branchToSync} --force`, { stdio: 'pipe' }); // execSync(`git -C "${localPath}" reset --hard origin/${branchToSync}`, { stdio: 'pipe' }); // Alternative to ensure it matches remote head console.log(`GitHubAdapter: Successfully synced ${repoConfig.owner}/${repoConfig.repo} to latest ${branchToSync}.`); } catch (error: any) { console.error(`GitHubAdapter: Failed to sync ${repoConfig.owner}/${repoConfig.repo} branch ${branchToSync}: ${error.message}`); if (error.stderr) { console.error(`GitHubAdapter: Git stderr: ${error.stderr.toString()}`); } if (error.stdout) { console.error(`GitHubAdapter: Git stdout: ${error.stdout.toString()}`); } throw error; } } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Lspace-io/lspace-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server