UNPKG

remcode

Version:

Turn your AI assistant into a codebase expert. Intelligent code analysis, semantic search, and software engineering guidance through MCP integration.

204 lines (203 loc) 8.42 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.GitHubClient = void 0; const logger_1 = require("../utils/logger"); const axios_1 = __importDefault(require("axios")); const uuid_1 = require("uuid"); const logger = (0, logger_1.getLogger)('GitHubClient'); class GitHubClient { constructor(options) { this.token = options.token; this.baseUrl = options.baseUrl || 'https://api.github.com'; this.maxRetries = options.maxRetries || 3; this.retryDelay = options.retryDelay || 1000; // Create axios instance with default config this.axios = axios_1.default.create({ baseURL: this.baseUrl, timeout: options.timeout || 30000, // Default 30 second timeout headers: { 'Accept': 'application/vnd.github+json', 'Authorization': `Bearer ${this.token}`, 'X-GitHub-Api-Version': '2022-11-28', // Using latest stable GitHub API version 'User-Agent': 'Remcode-GitHub-Client', } }); // Add response interceptor for logging this.axios.interceptors.response.use((response) => { const requestId = response.config['requestId']; logger.debug(`GitHub API response [${requestId}]: ${response.status}`); return response; }, (error) => { const requestId = error.config?.['requestId'] || 'unknown'; if (error.response) { logger.error(`GitHub API error [${requestId}]: ${error.response.status} - ${JSON.stringify(error.response.data)}`); } else { logger.error(`GitHub API error [${requestId}]: ${error.message}`); } return Promise.reject(error); }); } /** * Make a request to the GitHub API with automatic retries * @param endpoint API endpoint (starting with /) * @param method HTTP method * @param data Request data (for POST, PUT, PATCH) * @param config Additional axios config * @returns Promise with the response data */ async makeRequest(endpoint, method = 'GET', data, config = {}) { const requestId = (0, uuid_1.v4)().substring(0, 8); const retryConfig = { maxRetries: this.maxRetries, retryDelay: this.retryDelay, currentRetry: 0 }; logger.info(`GitHub API ${method} ${endpoint} [${requestId}]`); if (data) { logger.debug(`Request data [${requestId}]:`, data); } return this.makeRequestWithRetry(endpoint, method, data, config, retryConfig, requestId); } /** * Internal method to handle retries */ async makeRequestWithRetry(endpoint, method, data, config, retryConfig, requestId) { try { const mergedConfig = { ...config, requestId // Store request ID for logging in interceptors }; const response = await this.axios.request({ method, url: endpoint, data, ...mergedConfig }); return response.data; } catch (error) { if (!(error instanceof Error)) throw error; const axiosError = error; const status = axiosError.response?.status || 0; // Check if we should retry (network errors or specific status codes) const shouldRetry = (retryConfig.currentRetry < retryConfig.maxRetries && (!axiosError.response || status === 429 || // Rate limit status === 502 || // Bad gateway status === 503 || // Service unavailable status === 504 // Gateway timeout )); if (shouldRetry) { retryConfig.currentRetry += 1; const delay = retryConfig.retryDelay * Math.pow(2, retryConfig.currentRetry - 1); logger.warn(`Retrying GitHub API request [${requestId}] (${retryConfig.currentRetry}/${retryConfig.maxRetries}) after ${delay}ms`); await new Promise(resolve => setTimeout(resolve, delay)); return this.makeRequestWithRetry(endpoint, method, data, config, retryConfig, requestId); } // Check for rate limiting specifically if (status === 403 && axiosError.response?.headers?.['x-ratelimit-remaining'] === '0') { const resetTimestamp = axiosError.response?.headers?.['x-ratelimit-reset']; if (resetTimestamp) { const resetDate = new Date(parseInt(resetTimestamp) * 1000); logger.error(`GitHub API rate limit exceeded [${requestId}]. Resets at ${resetDate.toISOString()}`); } else { logger.error(`GitHub API rate limit exceeded [${requestId}]`); } } // Rethrow the error throw error; } } /** * Get information about a repository */ async getRepository(owner, repo) { return this.makeRequest(`/repos/${owner}/${repo}`); } /** * Set a repository secret */ async setRepositorySecret(owner, repo, secretName, secretValue) { // Get the repository's public key for secret encryption const publicKeyResponse = await this.makeRequest(`/repos/${owner}/${repo}/actions/secrets/public-key`); const { key, key_id } = publicKeyResponse; // Encrypt the secret using sodium (this requires the libsodium-wrappers package) // For now, this is just a placeholder for the actual encryption code logger.info(`Encrypting secret ${secretName} for repository ${owner}/${repo}`); const encryptedValue = Buffer.from(secretValue).toString('base64'); // This is a placeholder // Set the secret await this.makeRequest(`/repos/${owner}/${repo}/actions/secrets/${secretName}`, 'PUT', { encrypted_value: encryptedValue, key_id }); logger.info(`Secret ${secretName} set successfully for ${owner}/${repo}`); } /** * Trigger a workflow dispatch event */ async createWorkflowDispatch(owner, repo, workflowId, ref = 'main', inputs) { await this.makeRequest(`/repos/${owner}/${repo}/actions/workflows/${workflowId}/dispatches`, 'POST', { ref, inputs }); logger.info(`Triggered workflow ${workflowId} for ${owner}/${repo} on ref ${ref}`); } /** * Get information about a workflow run */ async getWorkflowRun(owner, repo, runId) { return this.makeRequest(`/repos/${owner}/${repo}/actions/runs/${runId}`); } /** * List workflow runs for a repository or workflow */ async listWorkflowRuns(owner, repo, workflowId) { const endpoint = workflowId ? `/repos/${owner}/${repo}/actions/workflows/${workflowId}/runs` : `/repos/${owner}/${repo}/actions/runs`; return this.makeRequest(endpoint); } /** * Create or update file contents */ async createOrUpdateFile(owner, repo, path, message, content, sha, branch) { const endpoint = `/repos/${owner}/${repo}/contents/${path}`; const data = { message, content: Buffer.from(content).toString('base64'), }; if (branch) { data.branch = branch; } if (sha) { data.sha = sha; } return this.makeRequest(endpoint, 'PUT', data); } /** * Create a new repository */ async createRepository(name, options = {}) { const data = { name, private: options.private || false, description: options.description || '', auto_init: options.autoInit || false, ...options }; return this.makeRequest('/user/repos', 'POST', data); } /** * Fork a repository */ async forkRepository(owner, repo, options = {}) { return this.makeRequest(`/repos/${owner}/${repo}/forks`, 'POST', options); } } exports.GitHubClient = GitHubClient;