mirror of
https://github.com/modelcontextprotocol/servers.git
synced 2026-04-19 08:43:28 +02:00
Merge main: Move to modular structure and add PR functionality
This commit is contained in:
112
src/github/operations/branches.ts
Normal file
112
src/github/operations/branches.ts
Normal file
@@ -0,0 +1,112 @@
|
||||
import { z } from "zod";
|
||||
import { githubRequest } from "../common/utils.js";
|
||||
import { GitHubReferenceSchema } from "../common/types.js";
|
||||
|
||||
// Schema definitions
|
||||
export const CreateBranchOptionsSchema = z.object({
|
||||
ref: z.string(),
|
||||
sha: z.string(),
|
||||
});
|
||||
|
||||
export const CreateBranchSchema = z.object({
|
||||
owner: z.string().describe("Repository owner (username or organization)"),
|
||||
repo: z.string().describe("Repository name"),
|
||||
branch: z.string().describe("Name for the new branch"),
|
||||
from_branch: z.string().optional().describe("Optional: source branch to create from (defaults to the repository's default branch)"),
|
||||
});
|
||||
|
||||
// Type exports
|
||||
export type CreateBranchOptions = z.infer<typeof CreateBranchOptionsSchema>;
|
||||
|
||||
// Function implementations
|
||||
export async function getDefaultBranchSHA(owner: string, repo: string): Promise<string> {
|
||||
try {
|
||||
const response = await githubRequest(
|
||||
`https://api.github.com/repos/${owner}/${repo}/git/refs/heads/main`
|
||||
);
|
||||
const data = GitHubReferenceSchema.parse(response);
|
||||
return data.object.sha;
|
||||
} catch (error) {
|
||||
const masterResponse = await githubRequest(
|
||||
`https://api.github.com/repos/${owner}/${repo}/git/refs/heads/master`
|
||||
);
|
||||
if (!masterResponse) {
|
||||
throw new Error("Could not find default branch (tried 'main' and 'master')");
|
||||
}
|
||||
const data = GitHubReferenceSchema.parse(masterResponse);
|
||||
return data.object.sha;
|
||||
}
|
||||
}
|
||||
|
||||
export async function createBranch(
|
||||
owner: string,
|
||||
repo: string,
|
||||
options: CreateBranchOptions
|
||||
): Promise<z.infer<typeof GitHubReferenceSchema>> {
|
||||
const fullRef = `refs/heads/${options.ref}`;
|
||||
|
||||
const response = await githubRequest(
|
||||
`https://api.github.com/repos/${owner}/${repo}/git/refs`,
|
||||
{
|
||||
method: "POST",
|
||||
body: {
|
||||
ref: fullRef,
|
||||
sha: options.sha,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
return GitHubReferenceSchema.parse(response);
|
||||
}
|
||||
|
||||
export async function getBranchSHA(
|
||||
owner: string,
|
||||
repo: string,
|
||||
branch: string
|
||||
): Promise<string> {
|
||||
const response = await githubRequest(
|
||||
`https://api.github.com/repos/${owner}/${repo}/git/refs/heads/${branch}`
|
||||
);
|
||||
|
||||
const data = GitHubReferenceSchema.parse(response);
|
||||
return data.object.sha;
|
||||
}
|
||||
|
||||
export async function createBranchFromRef(
|
||||
owner: string,
|
||||
repo: string,
|
||||
newBranch: string,
|
||||
fromBranch?: string
|
||||
): Promise<z.infer<typeof GitHubReferenceSchema>> {
|
||||
let sha: string;
|
||||
if (fromBranch) {
|
||||
sha = await getBranchSHA(owner, repo, fromBranch);
|
||||
} else {
|
||||
sha = await getDefaultBranchSHA(owner, repo);
|
||||
}
|
||||
|
||||
return createBranch(owner, repo, {
|
||||
ref: newBranch,
|
||||
sha,
|
||||
});
|
||||
}
|
||||
|
||||
export async function updateBranch(
|
||||
owner: string,
|
||||
repo: string,
|
||||
branch: string,
|
||||
sha: string
|
||||
): Promise<z.infer<typeof GitHubReferenceSchema>> {
|
||||
const response = await githubRequest(
|
||||
`https://api.github.com/repos/${owner}/${repo}/git/refs/heads/${branch}`,
|
||||
{
|
||||
method: "PATCH",
|
||||
body: {
|
||||
sha,
|
||||
force: true,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
return GitHubReferenceSchema.parse(response);
|
||||
}
|
||||
26
src/github/operations/commits.ts
Normal file
26
src/github/operations/commits.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { z } from "zod";
|
||||
import { githubRequest, buildUrl } from "../common/utils.js";
|
||||
|
||||
export const ListCommitsSchema = z.object({
|
||||
owner: z.string(),
|
||||
repo: z.string(),
|
||||
sha: z.string().optional(),
|
||||
page: z.number().optional(),
|
||||
perPage: z.number().optional()
|
||||
});
|
||||
|
||||
export async function listCommits(
|
||||
owner: string,
|
||||
repo: string,
|
||||
page?: number,
|
||||
perPage?: number,
|
||||
sha?: string
|
||||
) {
|
||||
return githubRequest(
|
||||
buildUrl(`https://api.github.com/repos/${owner}/${repo}/commits`, {
|
||||
page: page?.toString(),
|
||||
per_page: perPage?.toString(),
|
||||
sha
|
||||
})
|
||||
);
|
||||
}
|
||||
219
src/github/operations/files.ts
Normal file
219
src/github/operations/files.ts
Normal file
@@ -0,0 +1,219 @@
|
||||
import { z } from "zod";
|
||||
import { githubRequest } from "../common/utils.js";
|
||||
import {
|
||||
GitHubContentSchema,
|
||||
GitHubAuthorSchema,
|
||||
GitHubTreeSchema,
|
||||
GitHubCommitSchema,
|
||||
GitHubReferenceSchema,
|
||||
GitHubFileContentSchema,
|
||||
} from "../common/types.js";
|
||||
|
||||
// Schema definitions
|
||||
export const FileOperationSchema = z.object({
|
||||
path: z.string(),
|
||||
content: z.string(),
|
||||
});
|
||||
|
||||
export const CreateOrUpdateFileSchema = z.object({
|
||||
owner: z.string().describe("Repository owner (username or organization)"),
|
||||
repo: z.string().describe("Repository name"),
|
||||
path: z.string().describe("Path where to create/update the file"),
|
||||
content: z.string().describe("Content of the file"),
|
||||
message: z.string().describe("Commit message"),
|
||||
branch: z.string().describe("Branch to create/update the file in"),
|
||||
sha: z.string().optional().describe("SHA of the file being replaced (required when updating existing files)"),
|
||||
});
|
||||
|
||||
export const GetFileContentsSchema = z.object({
|
||||
owner: z.string().describe("Repository owner (username or organization)"),
|
||||
repo: z.string().describe("Repository name"),
|
||||
path: z.string().describe("Path to the file or directory"),
|
||||
branch: z.string().optional().describe("Branch to get contents from"),
|
||||
});
|
||||
|
||||
export const PushFilesSchema = z.object({
|
||||
owner: z.string().describe("Repository owner (username or organization)"),
|
||||
repo: z.string().describe("Repository name"),
|
||||
branch: z.string().describe("Branch to push to (e.g., 'main' or 'master')"),
|
||||
files: z.array(FileOperationSchema).describe("Array of files to push"),
|
||||
message: z.string().describe("Commit message"),
|
||||
});
|
||||
|
||||
export const GitHubCreateUpdateFileResponseSchema = z.object({
|
||||
content: GitHubFileContentSchema.nullable(),
|
||||
commit: z.object({
|
||||
sha: z.string(),
|
||||
node_id: z.string(),
|
||||
url: z.string(),
|
||||
html_url: z.string(),
|
||||
author: GitHubAuthorSchema,
|
||||
committer: GitHubAuthorSchema,
|
||||
message: z.string(),
|
||||
tree: z.object({
|
||||
sha: z.string(),
|
||||
url: z.string(),
|
||||
}),
|
||||
parents: z.array(
|
||||
z.object({
|
||||
sha: z.string(),
|
||||
url: z.string(),
|
||||
html_url: z.string(),
|
||||
})
|
||||
),
|
||||
}),
|
||||
});
|
||||
|
||||
// Type exports
|
||||
export type FileOperation = z.infer<typeof FileOperationSchema>;
|
||||
export type GitHubCreateUpdateFileResponse = z.infer<typeof GitHubCreateUpdateFileResponseSchema>;
|
||||
|
||||
// Function implementations
|
||||
export async function getFileContents(
|
||||
owner: string,
|
||||
repo: string,
|
||||
path: string,
|
||||
branch?: string
|
||||
) {
|
||||
let url = `https://api.github.com/repos/${owner}/${repo}/contents/${path}`;
|
||||
if (branch) {
|
||||
url += `?ref=${branch}`;
|
||||
}
|
||||
|
||||
const response = await githubRequest(url);
|
||||
const data = GitHubContentSchema.parse(response);
|
||||
|
||||
// If it's a file, decode the content
|
||||
if (!Array.isArray(data) && data.content) {
|
||||
data.content = Buffer.from(data.content, "base64").toString("utf8");
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
export async function createOrUpdateFile(
|
||||
owner: string,
|
||||
repo: string,
|
||||
path: string,
|
||||
content: string,
|
||||
message: string,
|
||||
branch: string,
|
||||
sha?: string
|
||||
) {
|
||||
const encodedContent = Buffer.from(content).toString("base64");
|
||||
|
||||
let currentSha = sha;
|
||||
if (!currentSha) {
|
||||
try {
|
||||
const existingFile = await getFileContents(owner, repo, path, branch);
|
||||
if (!Array.isArray(existingFile)) {
|
||||
currentSha = existingFile.sha;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Note: File does not exist in branch, will create new file");
|
||||
}
|
||||
}
|
||||
|
||||
const url = `https://api.github.com/repos/${owner}/${repo}/contents/${path}`;
|
||||
const body = {
|
||||
message,
|
||||
content: encodedContent,
|
||||
branch,
|
||||
...(currentSha ? { sha: currentSha } : {}),
|
||||
};
|
||||
|
||||
const response = await githubRequest(url, {
|
||||
method: "PUT",
|
||||
body,
|
||||
});
|
||||
|
||||
return GitHubCreateUpdateFileResponseSchema.parse(response);
|
||||
}
|
||||
|
||||
async function createTree(
|
||||
owner: string,
|
||||
repo: string,
|
||||
files: FileOperation[],
|
||||
baseTree?: string
|
||||
) {
|
||||
const tree = files.map((file) => ({
|
||||
path: file.path,
|
||||
mode: "100644" as const,
|
||||
type: "blob" as const,
|
||||
content: file.content,
|
||||
}));
|
||||
|
||||
const response = await githubRequest(
|
||||
`https://api.github.com/repos/${owner}/${repo}/git/trees`,
|
||||
{
|
||||
method: "POST",
|
||||
body: {
|
||||
tree,
|
||||
base_tree: baseTree,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
return GitHubTreeSchema.parse(response);
|
||||
}
|
||||
|
||||
async function createCommit(
|
||||
owner: string,
|
||||
repo: string,
|
||||
message: string,
|
||||
tree: string,
|
||||
parents: string[]
|
||||
) {
|
||||
const response = await githubRequest(
|
||||
`https://api.github.com/repos/${owner}/${repo}/git/commits`,
|
||||
{
|
||||
method: "POST",
|
||||
body: {
|
||||
message,
|
||||
tree,
|
||||
parents,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
return GitHubCommitSchema.parse(response);
|
||||
}
|
||||
|
||||
async function updateReference(
|
||||
owner: string,
|
||||
repo: string,
|
||||
ref: string,
|
||||
sha: string
|
||||
) {
|
||||
const response = await githubRequest(
|
||||
`https://api.github.com/repos/${owner}/${repo}/git/refs/${ref}`,
|
||||
{
|
||||
method: "PATCH",
|
||||
body: {
|
||||
sha,
|
||||
force: true,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
return GitHubReferenceSchema.parse(response);
|
||||
}
|
||||
|
||||
export async function pushFiles(
|
||||
owner: string,
|
||||
repo: string,
|
||||
branch: string,
|
||||
files: FileOperation[],
|
||||
message: string
|
||||
) {
|
||||
const refResponse = await githubRequest(
|
||||
`https://api.github.com/repos/${owner}/${repo}/git/refs/heads/${branch}`
|
||||
);
|
||||
|
||||
const ref = GitHubReferenceSchema.parse(refResponse);
|
||||
const commitSha = ref.object.sha;
|
||||
|
||||
const tree = await createTree(owner, repo, files, commitSha);
|
||||
const commit = await createCommit(owner, repo, message, tree.sha, [commitSha]);
|
||||
return await updateReference(owner, repo, `heads/${branch}`, commit.sha);
|
||||
}
|
||||
118
src/github/operations/issues.ts
Normal file
118
src/github/operations/issues.ts
Normal file
@@ -0,0 +1,118 @@
|
||||
import { z } from "zod";
|
||||
import { githubRequest, buildUrl } from "../common/utils.js";
|
||||
|
||||
export const GetIssueSchema = z.object({
|
||||
owner: z.string(),
|
||||
repo: z.string(),
|
||||
issue_number: z.number(),
|
||||
});
|
||||
|
||||
export const IssueCommentSchema = z.object({
|
||||
owner: z.string(),
|
||||
repo: z.string(),
|
||||
issue_number: z.number(),
|
||||
body: z.string(),
|
||||
});
|
||||
|
||||
export const CreateIssueOptionsSchema = z.object({
|
||||
title: z.string(),
|
||||
body: z.string().optional(),
|
||||
assignees: z.array(z.string()).optional(),
|
||||
milestone: z.number().optional(),
|
||||
labels: z.array(z.string()).optional(),
|
||||
});
|
||||
|
||||
export const CreateIssueSchema = z.object({
|
||||
owner: z.string(),
|
||||
repo: z.string(),
|
||||
...CreateIssueOptionsSchema.shape,
|
||||
});
|
||||
|
||||
export const ListIssuesOptionsSchema = z.object({
|
||||
owner: z.string(),
|
||||
repo: z.string(),
|
||||
direction: z.enum(["asc", "desc"]).optional(),
|
||||
labels: z.array(z.string()).optional(),
|
||||
page: z.number().optional(),
|
||||
per_page: z.number().optional(),
|
||||
since: z.string().optional(),
|
||||
sort: z.enum(["created", "updated", "comments"]).optional(),
|
||||
state: z.enum(["open", "closed", "all"]).optional(),
|
||||
});
|
||||
|
||||
export const UpdateIssueOptionsSchema = z.object({
|
||||
owner: z.string(),
|
||||
repo: z.string(),
|
||||
issue_number: z.number(),
|
||||
title: z.string().optional(),
|
||||
body: z.string().optional(),
|
||||
assignees: z.array(z.string()).optional(),
|
||||
milestone: z.number().optional(),
|
||||
labels: z.array(z.string()).optional(),
|
||||
state: z.enum(["open", "closed"]).optional(),
|
||||
});
|
||||
|
||||
export async function getIssue(owner: string, repo: string, issue_number: number) {
|
||||
return githubRequest(`https://api.github.com/repos/${owner}/${repo}/issues/${issue_number}`);
|
||||
}
|
||||
|
||||
export async function addIssueComment(
|
||||
owner: string,
|
||||
repo: string,
|
||||
issue_number: number,
|
||||
body: string
|
||||
) {
|
||||
return githubRequest(`https://api.github.com/repos/${owner}/${repo}/issues/${issue_number}/comments`, {
|
||||
method: "POST",
|
||||
body: { body },
|
||||
});
|
||||
}
|
||||
|
||||
export async function createIssue(
|
||||
owner: string,
|
||||
repo: string,
|
||||
options: z.infer<typeof CreateIssueOptionsSchema>
|
||||
) {
|
||||
return githubRequest(
|
||||
`https://api.github.com/repos/${owner}/${repo}/issues`,
|
||||
{
|
||||
method: "POST",
|
||||
body: options,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
export async function listIssues(
|
||||
owner: string,
|
||||
repo: string,
|
||||
options: Omit<z.infer<typeof ListIssuesOptionsSchema>, "owner" | "repo">
|
||||
) {
|
||||
const urlParams: Record<string, string | undefined> = {
|
||||
direction: options.direction,
|
||||
labels: options.labels?.join(","),
|
||||
page: options.page?.toString(),
|
||||
per_page: options.per_page?.toString(),
|
||||
since: options.since,
|
||||
sort: options.sort,
|
||||
state: options.state
|
||||
};
|
||||
|
||||
return githubRequest(
|
||||
buildUrl(`https://api.github.com/repos/${owner}/${repo}/issues`, urlParams)
|
||||
);
|
||||
}
|
||||
|
||||
export async function updateIssue(
|
||||
owner: string,
|
||||
repo: string,
|
||||
issue_number: number,
|
||||
options: Omit<z.infer<typeof UpdateIssueOptionsSchema>, "owner" | "repo" | "issue_number">
|
||||
) {
|
||||
return githubRequest(
|
||||
`https://api.github.com/repos/${owner}/${repo}/issues/${issue_number}`,
|
||||
{
|
||||
method: "PATCH",
|
||||
body: options,
|
||||
}
|
||||
);
|
||||
}
|
||||
275
src/github/operations/pulls.ts
Normal file
275
src/github/operations/pulls.ts
Normal file
@@ -0,0 +1,275 @@
|
||||
import { z } from "zod";
|
||||
import { githubRequest } from "../common/utils.js";
|
||||
import {
|
||||
GitHubPullRequestSchema,
|
||||
GitHubIssueAssigneeSchema,
|
||||
GitHubRepositorySchema,
|
||||
} from "../common/types.js";
|
||||
|
||||
// Schema definitions
|
||||
export const PullRequestFileSchema = z.object({
|
||||
sha: z.string(),
|
||||
filename: z.string(),
|
||||
status: z.enum(['added', 'removed', 'modified', 'renamed', 'copied', 'changed', 'unchanged']),
|
||||
additions: z.number(),
|
||||
deletions: z.number(),
|
||||
changes: z.number(),
|
||||
blob_url: z.string(),
|
||||
raw_url: z.string(),
|
||||
contents_url: z.string(),
|
||||
patch: z.string().optional()
|
||||
});
|
||||
|
||||
export const StatusCheckSchema = z.object({
|
||||
url: z.string(),
|
||||
state: z.enum(['error', 'failure', 'pending', 'success']),
|
||||
description: z.string().nullable(),
|
||||
target_url: z.string().nullable(),
|
||||
context: z.string(),
|
||||
created_at: z.string(),
|
||||
updated_at: z.string()
|
||||
});
|
||||
|
||||
export const CombinedStatusSchema = z.object({
|
||||
state: z.enum(['error', 'failure', 'pending', 'success']),
|
||||
statuses: z.array(StatusCheckSchema),
|
||||
sha: z.string(),
|
||||
total_count: z.number()
|
||||
});
|
||||
|
||||
export const PullRequestCommentSchema = z.object({
|
||||
url: z.string(),
|
||||
id: z.number(),
|
||||
node_id: z.string(),
|
||||
pull_request_review_id: z.number().nullable(),
|
||||
diff_hunk: z.string(),
|
||||
path: z.string().nullable(),
|
||||
position: z.number().nullable(),
|
||||
original_position: z.number().nullable(),
|
||||
commit_id: z.string(),
|
||||
original_commit_id: z.string(),
|
||||
user: GitHubIssueAssigneeSchema,
|
||||
body: z.string(),
|
||||
created_at: z.string(),
|
||||
updated_at: z.string(),
|
||||
html_url: z.string(),
|
||||
pull_request_url: z.string(),
|
||||
author_association: z.string(),
|
||||
_links: z.object({
|
||||
self: z.object({ href: z.string() }),
|
||||
html: z.object({ href: z.string() }),
|
||||
pull_request: z.object({ href: z.string() })
|
||||
})
|
||||
});
|
||||
|
||||
export const PullRequestReviewSchema = z.object({
|
||||
id: z.number(),
|
||||
node_id: z.string(),
|
||||
user: GitHubIssueAssigneeSchema,
|
||||
body: z.string().nullable(),
|
||||
state: z.enum(['APPROVED', 'CHANGES_REQUESTED', 'COMMENTED', 'DISMISSED', 'PENDING']),
|
||||
html_url: z.string(),
|
||||
pull_request_url: z.string(),
|
||||
commit_id: z.string(),
|
||||
submitted_at: z.string().nullable(),
|
||||
author_association: z.string()
|
||||
});
|
||||
|
||||
// Input schemas
|
||||
export const GetPullRequestSchema = z.object({
|
||||
owner: z.string().describe("Repository owner (username or organization)"),
|
||||
repo: z.string().describe("Repository name"),
|
||||
pull_number: z.number().describe("Pull request number")
|
||||
});
|
||||
|
||||
export const ListPullRequestsSchema = z.object({
|
||||
owner: z.string().describe("Repository owner (username or organization)"),
|
||||
repo: z.string().describe("Repository name"),
|
||||
state: z.enum(['open', 'closed', 'all']).optional().describe("State of the pull requests to return"),
|
||||
head: z.string().optional().describe("Filter by head user or head organization and branch name"),
|
||||
base: z.string().optional().describe("Filter by base branch name"),
|
||||
sort: z.enum(['created', 'updated', 'popularity', 'long-running']).optional().describe("What to sort results by"),
|
||||
direction: z.enum(['asc', 'desc']).optional().describe("The direction of the sort"),
|
||||
per_page: z.number().optional().describe("Results per page (max 100)"),
|
||||
page: z.number().optional().describe("Page number of the results")
|
||||
});
|
||||
|
||||
export const CreatePullRequestReviewSchema = z.object({
|
||||
owner: z.string().describe("Repository owner (username or organization)"),
|
||||
repo: z.string().describe("Repository name"),
|
||||
pull_number: z.number().describe("Pull request number"),
|
||||
commit_id: z.string().optional().describe("The SHA of the commit that needs a review"),
|
||||
body: z.string().describe("The body text of the review"),
|
||||
event: z.enum(['APPROVE', 'REQUEST_CHANGES', 'COMMENT']).describe("The review action to perform"),
|
||||
comments: z.array(z.object({
|
||||
path: z.string().describe("The relative path to the file being commented on"),
|
||||
position: z.number().describe("The position in the diff where you want to add a review comment"),
|
||||
body: z.string().describe("Text of the review comment")
|
||||
})).optional().describe("Comments to post as part of the review")
|
||||
});
|
||||
|
||||
export const MergePullRequestSchema = z.object({
|
||||
owner: z.string().describe("Repository owner (username or organization)"),
|
||||
repo: z.string().describe("Repository name"),
|
||||
pull_number: z.number().describe("Pull request number"),
|
||||
commit_title: z.string().optional().describe("Title for the automatic commit message"),
|
||||
commit_message: z.string().optional().describe("Extra detail to append to automatic commit message"),
|
||||
merge_method: z.enum(['merge', 'squash', 'rebase']).optional().describe("Merge method to use")
|
||||
});
|
||||
|
||||
export const GetPullRequestFilesSchema = z.object({
|
||||
owner: z.string().describe("Repository owner (username or organization)"),
|
||||
repo: z.string().describe("Repository name"),
|
||||
pull_number: z.number().describe("Pull request number")
|
||||
});
|
||||
|
||||
export const GetPullRequestStatusSchema = z.object({
|
||||
owner: z.string().describe("Repository owner (username or organization)"),
|
||||
repo: z.string().describe("Repository name"),
|
||||
pull_number: z.number().describe("Pull request number")
|
||||
});
|
||||
|
||||
export const UpdatePullRequestBranchSchema = z.object({
|
||||
owner: z.string().describe("Repository owner (username or organization)"),
|
||||
repo: z.string().describe("Repository name"),
|
||||
pull_number: z.number().describe("Pull request number"),
|
||||
expected_head_sha: z.string().optional().describe("The expected SHA of the pull request's HEAD ref")
|
||||
});
|
||||
|
||||
export const GetPullRequestCommentsSchema = z.object({
|
||||
owner: z.string().describe("Repository owner (username or organization)"),
|
||||
repo: z.string().describe("Repository name"),
|
||||
pull_number: z.number().describe("Pull request number")
|
||||
});
|
||||
|
||||
export const GetPullRequestReviewsSchema = z.object({
|
||||
owner: z.string().describe("Repository owner (username or organization)"),
|
||||
repo: z.string().describe("Repository name"),
|
||||
pull_number: z.number().describe("Pull request number")
|
||||
});
|
||||
|
||||
// Function implementations
|
||||
export async function getPullRequest(
|
||||
owner: string,
|
||||
repo: string,
|
||||
pullNumber: number
|
||||
): Promise<z.infer<typeof GitHubPullRequestSchema>> {
|
||||
const response = await githubRequest(
|
||||
`https://api.github.com/repos/${owner}/${repo}/pulls/${pullNumber}`
|
||||
);
|
||||
return GitHubPullRequestSchema.parse(response);
|
||||
}
|
||||
|
||||
export async function listPullRequests(
|
||||
owner: string,
|
||||
repo: string,
|
||||
options: Omit<z.infer<typeof ListPullRequestsSchema>, 'owner' | 'repo'>
|
||||
): Promise<z.infer<typeof GitHubPullRequestSchema>[]> {
|
||||
const url = new URL(`https://api.github.com/repos/${owner}/${repo}/pulls`);
|
||||
|
||||
if (options.state) url.searchParams.append('state', options.state);
|
||||
if (options.head) url.searchParams.append('head', options.head);
|
||||
if (options.base) url.searchParams.append('base', options.base);
|
||||
if (options.sort) url.searchParams.append('sort', options.sort);
|
||||
if (options.direction) url.searchParams.append('direction', options.direction);
|
||||
if (options.per_page) url.searchParams.append('per_page', options.per_page.toString());
|
||||
if (options.page) url.searchParams.append('page', options.page.toString());
|
||||
|
||||
const response = await githubRequest(url.toString());
|
||||
return z.array(GitHubPullRequestSchema).parse(response);
|
||||
}
|
||||
|
||||
export async function createPullRequestReview(
|
||||
owner: string,
|
||||
repo: string,
|
||||
pullNumber: number,
|
||||
options: Omit<z.infer<typeof CreatePullRequestReviewSchema>, 'owner' | 'repo' | 'pull_number'>
|
||||
): Promise<z.infer<typeof PullRequestReviewSchema>> {
|
||||
const response = await githubRequest(
|
||||
`https://api.github.com/repos/${owner}/${repo}/pulls/${pullNumber}/reviews`,
|
||||
{
|
||||
method: 'POST',
|
||||
body: options,
|
||||
}
|
||||
);
|
||||
return PullRequestReviewSchema.parse(response);
|
||||
}
|
||||
|
||||
export async function mergePullRequest(
|
||||
owner: string,
|
||||
repo: string,
|
||||
pullNumber: number,
|
||||
options: Omit<z.infer<typeof MergePullRequestSchema>, 'owner' | 'repo' | 'pull_number'>
|
||||
): Promise<any> {
|
||||
return githubRequest(
|
||||
`https://api.github.com/repos/${owner}/${repo}/pulls/${pullNumber}/merge`,
|
||||
{
|
||||
method: 'PUT',
|
||||
body: options,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
export async function getPullRequestFiles(
|
||||
owner: string,
|
||||
repo: string,
|
||||
pullNumber: number
|
||||
): Promise<z.infer<typeof PullRequestFileSchema>[]> {
|
||||
const response = await githubRequest(
|
||||
`https://api.github.com/repos/${owner}/${repo}/pulls/${pullNumber}/files`
|
||||
);
|
||||
return z.array(PullRequestFileSchema).parse(response);
|
||||
}
|
||||
|
||||
export async function updatePullRequestBranch(
|
||||
owner: string,
|
||||
repo: string,
|
||||
pullNumber: number,
|
||||
expectedHeadSha?: string
|
||||
): Promise<void> {
|
||||
await githubRequest(
|
||||
`https://api.github.com/repos/${owner}/${repo}/pulls/${pullNumber}/update-branch`,
|
||||
{
|
||||
method: "PUT",
|
||||
body: expectedHeadSha ? { expected_head_sha: expectedHeadSha } : undefined,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
export async function getPullRequestComments(
|
||||
owner: string,
|
||||
repo: string,
|
||||
pullNumber: number
|
||||
): Promise<z.infer<typeof PullRequestCommentSchema>[]> {
|
||||
const response = await githubRequest(
|
||||
`https://api.github.com/repos/${owner}/${repo}/pulls/${pullNumber}/comments`
|
||||
);
|
||||
return z.array(PullRequestCommentSchema).parse(response);
|
||||
}
|
||||
|
||||
export async function getPullRequestReviews(
|
||||
owner: string,
|
||||
repo: string,
|
||||
pullNumber: number
|
||||
): Promise<z.infer<typeof PullRequestReviewSchema>[]> {
|
||||
const response = await githubRequest(
|
||||
`https://api.github.com/repos/${owner}/${repo}/pulls/${pullNumber}/reviews`
|
||||
);
|
||||
return z.array(PullRequestReviewSchema).parse(response);
|
||||
}
|
||||
|
||||
export async function getPullRequestStatus(
|
||||
owner: string,
|
||||
repo: string,
|
||||
pullNumber: number
|
||||
): Promise<z.infer<typeof CombinedStatusSchema>> {
|
||||
// First get the PR to get the head SHA
|
||||
const pr = await getPullRequest(owner, repo, pullNumber);
|
||||
const sha = pr.head.sha;
|
||||
|
||||
// Then get the combined status for that SHA
|
||||
const response = await githubRequest(
|
||||
`https://api.github.com/repos/${owner}/${repo}/commits/${sha}/status`
|
||||
);
|
||||
return CombinedStatusSchema.parse(response);
|
||||
}
|
||||
65
src/github/operations/repository.ts
Normal file
65
src/github/operations/repository.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
import { z } from "zod";
|
||||
import { githubRequest } from "../common/utils.js";
|
||||
import { GitHubRepositorySchema, GitHubSearchResponseSchema } from "../common/types.js";
|
||||
|
||||
// Schema definitions
|
||||
export const CreateRepositoryOptionsSchema = z.object({
|
||||
name: z.string().describe("Repository name"),
|
||||
description: z.string().optional().describe("Repository description"),
|
||||
private: z.boolean().optional().describe("Whether the repository should be private"),
|
||||
autoInit: z.boolean().optional().describe("Initialize with README.md"),
|
||||
});
|
||||
|
||||
export const SearchRepositoriesSchema = z.object({
|
||||
query: z.string().describe("Search query (see GitHub search syntax)"),
|
||||
page: z.number().optional().describe("Page number for pagination (default: 1)"),
|
||||
perPage: z.number().optional().describe("Number of results per page (default: 30, max: 100)"),
|
||||
});
|
||||
|
||||
export const ForkRepositorySchema = z.object({
|
||||
owner: z.string().describe("Repository owner (username or organization)"),
|
||||
repo: z.string().describe("Repository name"),
|
||||
organization: z.string().optional().describe("Optional: organization to fork to (defaults to your personal account)"),
|
||||
});
|
||||
|
||||
// Type exports
|
||||
export type CreateRepositoryOptions = z.infer<typeof CreateRepositoryOptionsSchema>;
|
||||
|
||||
// Function implementations
|
||||
export async function createRepository(options: CreateRepositoryOptions) {
|
||||
const response = await githubRequest("https://api.github.com/user/repos", {
|
||||
method: "POST",
|
||||
body: options,
|
||||
});
|
||||
return GitHubRepositorySchema.parse(response);
|
||||
}
|
||||
|
||||
export async function searchRepositories(
|
||||
query: string,
|
||||
page: number = 1,
|
||||
perPage: number = 30
|
||||
) {
|
||||
const url = new URL("https://api.github.com/search/repositories");
|
||||
url.searchParams.append("q", query);
|
||||
url.searchParams.append("page", page.toString());
|
||||
url.searchParams.append("per_page", perPage.toString());
|
||||
|
||||
const response = await githubRequest(url.toString());
|
||||
return GitHubSearchResponseSchema.parse(response);
|
||||
}
|
||||
|
||||
export async function forkRepository(
|
||||
owner: string,
|
||||
repo: string,
|
||||
organization?: string
|
||||
) {
|
||||
const url = organization
|
||||
? `https://api.github.com/repos/${owner}/${repo}/forks?organization=${organization}`
|
||||
: `https://api.github.com/repos/${owner}/${repo}/forks`;
|
||||
|
||||
const response = await githubRequest(url, { method: "POST" });
|
||||
return GitHubRepositorySchema.extend({
|
||||
parent: GitHubRepositorySchema,
|
||||
source: GitHubRepositorySchema,
|
||||
}).parse(response);
|
||||
}
|
||||
45
src/github/operations/search.ts
Normal file
45
src/github/operations/search.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import { z } from "zod";
|
||||
import { githubRequest, buildUrl } from "../common/utils.js";
|
||||
|
||||
export const SearchOptions = z.object({
|
||||
q: z.string(),
|
||||
order: z.enum(["asc", "desc"]).optional(),
|
||||
page: z.number().min(1).optional(),
|
||||
per_page: z.number().min(1).max(100).optional(),
|
||||
});
|
||||
|
||||
export const SearchUsersOptions = SearchOptions.extend({
|
||||
sort: z.enum(["followers", "repositories", "joined"]).optional(),
|
||||
});
|
||||
|
||||
export const SearchIssuesOptions = SearchOptions.extend({
|
||||
sort: z.enum([
|
||||
"comments",
|
||||
"reactions",
|
||||
"reactions-+1",
|
||||
"reactions--1",
|
||||
"reactions-smile",
|
||||
"reactions-thinking_face",
|
||||
"reactions-heart",
|
||||
"reactions-tada",
|
||||
"interactions",
|
||||
"created",
|
||||
"updated",
|
||||
]).optional(),
|
||||
});
|
||||
|
||||
export const SearchCodeSchema = SearchOptions;
|
||||
export const SearchUsersSchema = SearchUsersOptions;
|
||||
export const SearchIssuesSchema = SearchIssuesOptions;
|
||||
|
||||
export async function searchCode(params: z.infer<typeof SearchCodeSchema>) {
|
||||
return githubRequest(buildUrl("https://api.github.com/search/code", params));
|
||||
}
|
||||
|
||||
export async function searchIssues(params: z.infer<typeof SearchIssuesSchema>) {
|
||||
return githubRequest(buildUrl("https://api.github.com/search/issues", params));
|
||||
}
|
||||
|
||||
export async function searchUsers(params: z.infer<typeof SearchUsersSchema>) {
|
||||
return githubRequest(buildUrl("https://api.github.com/search/users", params));
|
||||
}
|
||||
Reference in New Issue
Block a user