sushil-gitmate
Version:
Professional Git workflow automation powered by AI. Streamline your development process with natural language commands and intelligent automation.
213 lines (201 loc) • 8.85 kB
JavaScript
import axios from 'axios';
import dotenv from 'dotenv';
import logger from '../utils/logger.js';
import { getToken } from '../utils/tokenManager.js';
dotenv.config();
const GITHUB_API_BASE_URL = process.env.GITHUB_API_BASE_URL || 'https://api.github.com';
async function getHeaders() {
const token = await getToken('github_access_token');
if (!token) {
logger.error('GitHub access token not found. Please authenticate first.', { service: 'GitHubService' });
throw new Error('GitHub access token not found. Run authentication flow.');
}
return {
Authorization: `token ${token}`,
Accept: 'application/vnd.github.v3+json',
};
}
/**
* Fetches the authenticated user's profile.
* @returns {Promise<object>} User profile data.
*/
export async function getUserProfile() {
try {
const headers = await getHeaders();
const response = await axios.get(`${GITHUB_API_BASE_URL}/user`, { headers });
logger.info('Successfully fetched user profile.', { user: response.data.login, service: 'GitHubService' });
return response.data;
} catch (error) {
logger.error('Error fetching user profile:', {
message: error.message,
status: error.response?.status,
data: error.response?.data,
service: 'GitHubService',
});
throw error; // Re-throw to be handled by the caller
}
}
/**
* Lists repositories for the authenticated user.
* @param {object} options - Options for listing repositories (e.g., type, sort, direction, per_page, page).
* @returns {Promise<Array>} List of repositories.
*/
export async function listUserRepositories(options = {}) {
try {
const headers = await getHeaders();
const params = new URLSearchParams(options).toString();
const url = `${GITHUB_API_BASE_URL}/user/repos${params ? `?${params}` : ''}`;
logger.info(`Fetching user repositories with options: ${JSON.stringify(options)}`, { service: 'GitHubService' });
const response = await axios.get(url, { headers });
logger.info(`Successfully fetched ${response.data.length} repositories.`, { service: 'GitHubService' });
return response.data;
} catch (error) {
logger.error('Error listing user repositories:', {
message: error.message,
status: error.response?.status,
data: error.response?.data,
service: 'GitHubService',
});
throw error;
}
}
/**
* Creates a new repository for the authenticated user.
* @param {string} name - The name of the repository.
* @param {object} options - Repository options (e.g., description, private, auto_init).
* Example: { description: 'My new repo', private: false, auto_init: true }
* @returns {Promise<object>} The created repository data.
*/
export async function createRepository(name, options = {}) {
if (!name || typeof name !== 'string' || name.trim() === '') {
const err = new Error('Repository name is required and must be a non-empty string.');
logger.error(err.message, { name, options, service: 'GitHubService' });
throw err;
}
try {
const headers = await getHeaders();
const payload = {
name,
description: options.description || '',
private: options.private === undefined ? false : Boolean(options.private), // Default to public
auto_init: options.auto_init === undefined ? false : Boolean(options.auto_init), // Default to no auto_init
...options, // Allow other valid GitHub API options
};
logger.info(`Creating repository "${name}" with options: ${JSON.stringify(payload)}`, { service: 'GitHubService' });
const response = await axios.post(`${GITHUB_API_BASE_URL}/user/repos`, payload, { headers });
logger.info(`Successfully created repository: ${response.data.full_name}`, { service: 'GitHubService' });
return response.data;
} catch (error) {
logger.error(`Error creating repository "${name}":`, {
message: error.message,
status: error.response?.status,
data: error.response?.data,
payloadSent: { name, ...options },
service: 'GitHubService',
});
throw error;
}
}
/**
* Gets details for a specific repository.
* @param {string} owner - The owner of the repository.
* @param {string} repo - The name of the repository.
* @returns {Promise<object>} Repository details.
*/
export async function getRepositoryDetails(owner, repo) {
if (!owner || !repo) {
const err = new Error('Owner and repository name are required.');
logger.error(err.message, { owner, repo, service: 'GitHubService' });
throw err;
}
try {
const headers = await getHeaders();
logger.info(`Fetching details for repository: ${owner}/${repo}`, { service: 'GitHubService' });
const response = await axios.get(`${GITHUB_API_BASE_URL}/repos/${owner}/${repo}`, { headers });
logger.info(`Successfully fetched details for ${owner}/${repo}.`, { service: 'GitHubService' });
return response.data;
} catch (error) {
logger.error(`Error fetching repository details for ${owner}/${repo}:`, {
message: error.message,
status: error.response?.status,
data: error.response?.data,
service: 'GitHubService',
});
throw error;
}
}
/**
* Creates a new branch in a repository.
* @param {string} owner - The owner of the repository.
* @param {string} repo - The name of the repository.
* @param {string} branchName - The name of the new branch.
* @param {string} sha - The SHA of the commit to base the new branch on.
* @returns {Promise<object>} The created branch reference data.
*/
export async function createBranch(owner, repo, branchName, sha) {
if (!owner || !repo || !branchName || !sha) {
const err = new Error('Owner, repo, branch name, and SHA are required to create a branch.');
logger.error(err.message, { owner, repo, branchName, sha, service: 'GitHubService' });
throw err;
}
try {
const headers = await getHeaders();
const payload = {
ref: `refs/heads/${branchName}`,
sha: sha,
};
logger.info(`Creating branch "${branchName}" in ${owner}/${repo} from SHA ${sha}`, { service: 'GitHubService' });
const response = await axios.post(`${GITHUB_API_BASE_URL}/repos/${owner}/${repo}/git/refs`, payload, { headers });
logger.info(`Successfully created branch: ${response.data.ref}`, { service: 'GitHubService' });
return response.data;
} catch (error) {
logger.error(`Error creating branch "${branchName}" in ${owner}/${repo}:`, {
message: error.message,
status: error.response?.status,
data: error.response?.data,
service: 'GitHubService',
});
throw error;
}
}
/**
* Creates a new pull request.
* @param {string} owner - The owner of the repository.
* @param {string} repo - The name of the repository.
* @param {string} title - The title of the pull request.
* @param {string} head - The name of the branch where your changes are implemented.
* @param {string} base - The name of the branch you want the changes pulled into.
* @param {string} body - (Optional) The contents of the pull request.
* @returns {Promise<object>} The created pull request data.
*/
export async function createPullRequest(owner, repo, title, head, base, body = '') {
if (!owner || !repo || !title || !head || !base) {
const err = new Error('Owner, repo, title, head branch, and base branch are required for a pull request.');
logger.error(err.message, { owner, repo, title, head, base, service: 'GitHubService' });
throw err;
}
try {
const headers = await getHeaders();
const payload = { title, head, base, body };
logger.info(`Creating pull request in ${owner}/${repo}: "${title}" from ${head} to ${base}`, { service: 'GitHubService' });
const response = await axios.post(`${GITHUB_API_BASE_URL}/repos/${owner}/${repo}/pulls`, payload, { headers });
logger.info(`Successfully created pull request: #${response.data.number}`, { service: 'GitHubService' });
return response.data;
} catch (error) {
logger.error(`Error creating pull request in ${owner}/${repo}:`, {
message: error.message,
status: error.response?.status,
data: error.response?.data,
service: 'GitHubService',
});
// GitHub often returns 422 with specific errors in response.data.errors
if (error.response && error.response.data && error.response.data.errors) {
logger.error('Detailed errors from GitHub:', { errors: error.response.data.errors, service: 'GitHubService' });
}
throw error;
}
}
// TODO: Implement other GitHub API functions as needed:
// - Pushing code (more complex, involves local git operations first, then updating refs or creating blobs/trees/commits)
// - Handling repo permissions
// - Getting file content, updating files, etc.