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
JavaScript
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;
;