mirror of
https://github.com/modelcontextprotocol/servers.git
synced 2026-04-17 23:53:24 +02:00
refactor: improve pull request schemas and validation
- Add proper state enum validation - Add title and body length validation - Consolidate request schemas - Add consistent parameter handling - Improve type safety - Add proper JSDoc documentation
This commit is contained in:
@@ -5,71 +5,122 @@ import {
|
||||
GitHubRepositorySchema
|
||||
} from "../common/types.js";
|
||||
|
||||
// Schema definitions
|
||||
export const GitHubPullRequestHeadSchema = z.object({
|
||||
// Constants for GitHub limits and constraints
|
||||
const GITHUB_TITLE_MAX_LENGTH = 256;
|
||||
const GITHUB_BODY_MAX_LENGTH = 65536;
|
||||
|
||||
// Base schema for repository identification
|
||||
export const RepositoryParamsSchema = z.object({
|
||||
owner: z.string().min(1).describe("Repository owner (username or organization)"),
|
||||
repo: z.string().min(1).describe("Repository name"),
|
||||
});
|
||||
|
||||
// Common validation schemas
|
||||
export const GitHubPullRequestStateSchema = z.enum([
|
||||
"open",
|
||||
"closed",
|
||||
"merged",
|
||||
"draft"
|
||||
]).describe("The current state of the pull request");
|
||||
|
||||
export const GitHubPullRequestSortSchema = z.enum([
|
||||
"created",
|
||||
"updated",
|
||||
"popularity",
|
||||
"long-running"
|
||||
]).describe("The sorting field for pull requests");
|
||||
|
||||
export const GitHubDirectionSchema = z.enum([
|
||||
"asc",
|
||||
"desc"
|
||||
]).describe("The sort direction");
|
||||
|
||||
// Pull request head/base schema
|
||||
export const GitHubPullRequestRefSchema = z.object({
|
||||
label: z.string(),
|
||||
ref: z.string(),
|
||||
sha: z.string(),
|
||||
ref: z.string().min(1),
|
||||
sha: z.string().length(40),
|
||||
user: GitHubIssueAssigneeSchema,
|
||||
repo: GitHubRepositorySchema,
|
||||
});
|
||||
}).describe("Reference information for pull request head or base");
|
||||
|
||||
// Main pull request schema
|
||||
export const GitHubPullRequestSchema = z.object({
|
||||
url: z.string(),
|
||||
id: z.number(),
|
||||
url: z.string().url(),
|
||||
id: z.number().positive(),
|
||||
node_id: z.string(),
|
||||
html_url: z.string(),
|
||||
diff_url: z.string(),
|
||||
patch_url: z.string(),
|
||||
issue_url: z.string(),
|
||||
number: z.number(),
|
||||
state: z.string(),
|
||||
html_url: z.string().url(),
|
||||
diff_url: z.string().url(),
|
||||
patch_url: z.string().url(),
|
||||
issue_url: z.string().url(),
|
||||
number: z.number().positive(),
|
||||
state: GitHubPullRequestStateSchema,
|
||||
locked: z.boolean(),
|
||||
title: z.string(),
|
||||
title: z.string().max(GITHUB_TITLE_MAX_LENGTH),
|
||||
user: GitHubIssueAssigneeSchema,
|
||||
body: z.string(),
|
||||
created_at: z.string(),
|
||||
updated_at: z.string(),
|
||||
closed_at: z.string().nullable(),
|
||||
merged_at: z.string().nullable(),
|
||||
merge_commit_sha: z.string().nullable(),
|
||||
body: z.string().max(GITHUB_BODY_MAX_LENGTH).nullable(),
|
||||
created_at: z.string().datetime(),
|
||||
updated_at: z.string().datetime(),
|
||||
closed_at: z.string().datetime().nullable(),
|
||||
merged_at: z.string().datetime().nullable(),
|
||||
merge_commit_sha: z.string().length(40).nullable(),
|
||||
assignee: GitHubIssueAssigneeSchema.nullable(),
|
||||
assignees: z.array(GitHubIssueAssigneeSchema),
|
||||
head: GitHubPullRequestHeadSchema,
|
||||
base: GitHubPullRequestHeadSchema,
|
||||
requested_reviewers: z.array(GitHubIssueAssigneeSchema),
|
||||
labels: z.array(z.object({
|
||||
name: z.string(),
|
||||
color: z.string().regex(/^[0-9a-fA-F]{6}$/),
|
||||
description: z.string().nullable(),
|
||||
})),
|
||||
head: GitHubPullRequestRefSchema,
|
||||
base: GitHubPullRequestRefSchema,
|
||||
});
|
||||
|
||||
// Request schemas
|
||||
export const ListPullRequestsOptionsSchema = z.object({
|
||||
state: GitHubPullRequestStateSchema.optional(),
|
||||
head: z.string().optional(),
|
||||
base: z.string().optional(),
|
||||
sort: GitHubPullRequestSortSchema.optional(),
|
||||
direction: GitHubDirectionSchema.optional(),
|
||||
per_page: z.number().min(1).max(100).optional(),
|
||||
page: z.number().min(1).optional(),
|
||||
}).describe("Options for listing pull requests");
|
||||
|
||||
export const CreatePullRequestOptionsSchema = z.object({
|
||||
title: z.string().describe("Pull request title"),
|
||||
body: z.string().optional().describe("Pull request body/description"),
|
||||
head: z.string().describe("The name of the branch where your changes are implemented"),
|
||||
base: z.string().describe("The name of the branch you want the changes pulled into"),
|
||||
title: z.string().max(GITHUB_TITLE_MAX_LENGTH).describe("Pull request title"),
|
||||
body: z.string().max(GITHUB_BODY_MAX_LENGTH).optional().describe("Pull request body/description"),
|
||||
head: z.string().min(1).describe("The name of the branch where your changes are implemented"),
|
||||
base: z.string().min(1).describe("The name of the branch you want the changes pulled into"),
|
||||
maintainer_can_modify: z.boolean().optional().describe("Whether maintainers can modify the pull request"),
|
||||
draft: z.boolean().optional().describe("Whether to create the pull request as a draft"),
|
||||
});
|
||||
}).describe("Options for creating a pull request");
|
||||
|
||||
export const CreatePullRequestSchema = z.object({
|
||||
owner: z.string().describe("Repository owner (username or organization)"),
|
||||
repo: z.string().describe("Repository name"),
|
||||
title: z.string().describe("Pull request title"),
|
||||
body: z.string().optional().describe("Pull request body/description"),
|
||||
head: z.string().describe("The name of the branch where your changes are implemented"),
|
||||
base: z.string().describe("The name of the branch you want the changes pulled into"),
|
||||
draft: z.boolean().optional().describe("Whether to create the pull request as a draft"),
|
||||
maintainer_can_modify: z.boolean().optional().describe("Whether maintainers can modify the pull request"),
|
||||
// Combine repository params with operation options
|
||||
export const CreatePullRequestSchema = RepositoryParamsSchema.extend({
|
||||
...CreatePullRequestOptionsSchema.shape,
|
||||
});
|
||||
|
||||
// Type exports
|
||||
export type RepositoryParams = z.infer<typeof RepositoryParamsSchema>;
|
||||
export type CreatePullRequestOptions = z.infer<typeof CreatePullRequestOptionsSchema>;
|
||||
export type ListPullRequestsOptions = z.infer<typeof ListPullRequestsOptionsSchema>;
|
||||
export type GitHubPullRequest = z.infer<typeof GitHubPullRequestSchema>;
|
||||
export type GitHubPullRequestHead = z.infer<typeof GitHubPullRequestHeadSchema>;
|
||||
export type GitHubPullRequestRef = z.infer<typeof GitHubPullRequestRefSchema>;
|
||||
|
||||
// Function implementations
|
||||
/**
|
||||
* Creates a new pull request in a repository.
|
||||
*
|
||||
* @param params Repository identification and pull request creation options
|
||||
* @returns Promise resolving to the created pull request
|
||||
* @throws {ZodError} If the input parameters fail validation
|
||||
* @throws {Error} If the GitHub API request fails
|
||||
*/
|
||||
export async function createPullRequest(
|
||||
owner: string,
|
||||
repo: string,
|
||||
options: CreatePullRequestOptions
|
||||
): Promise<z.infer<typeof GitHubPullRequestSchema>> {
|
||||
params: z.infer<typeof CreatePullRequestSchema>
|
||||
): Promise<GitHubPullRequest> {
|
||||
const { owner, repo, ...options } = CreatePullRequestSchema.parse(params);
|
||||
|
||||
const response = await githubRequest(
|
||||
`https://api.github.com/repos/${owner}/${repo}/pulls`,
|
||||
{
|
||||
@@ -81,11 +132,21 @@ export async function createPullRequest(
|
||||
return GitHubPullRequestSchema.parse(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a specific pull request by its number.
|
||||
*
|
||||
* @param params Repository parameters and pull request number
|
||||
* @returns Promise resolving to the pull request details
|
||||
* @throws {Error} If the pull request is not found or the request fails
|
||||
*/
|
||||
export async function getPullRequest(
|
||||
owner: string,
|
||||
repo: string,
|
||||
pullNumber: number
|
||||
): Promise<z.infer<typeof GitHubPullRequestSchema>> {
|
||||
params: RepositoryParams & { pullNumber: number }
|
||||
): Promise<GitHubPullRequest> {
|
||||
const { owner, repo, pullNumber } = z.object({
|
||||
...RepositoryParamsSchema.shape,
|
||||
pullNumber: z.number().positive(),
|
||||
}).parse(params);
|
||||
|
||||
const response = await githubRequest(
|
||||
`https://api.github.com/repos/${owner}/${repo}/pulls/${pullNumber}`
|
||||
);
|
||||
@@ -93,20 +154,24 @@ export async function getPullRequest(
|
||||
return GitHubPullRequestSchema.parse(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Lists pull requests in a repository with optional filtering.
|
||||
*
|
||||
* @param params Repository parameters and listing options
|
||||
* @returns Promise resolving to an array of pull requests
|
||||
* @throws {ZodError} If the input parameters fail validation
|
||||
* @throws {Error} If the GitHub API request fails
|
||||
*/
|
||||
export async function listPullRequests(
|
||||
owner: string,
|
||||
repo: string,
|
||||
options: {
|
||||
state?: "open" | "closed" | "all";
|
||||
head?: string;
|
||||
base?: string;
|
||||
sort?: "created" | "updated" | "popularity" | "long-running";
|
||||
direction?: "asc" | "desc";
|
||||
per_page?: number;
|
||||
page?: number;
|
||||
} = {}
|
||||
): Promise<z.infer<typeof GitHubPullRequestSchema>[]> {
|
||||
params: RepositoryParams & Partial<ListPullRequestsOptions>
|
||||
): Promise<GitHubPullRequest[]> {
|
||||
const { owner, repo, ...options } = z.object({
|
||||
...RepositoryParamsSchema.shape,
|
||||
...ListPullRequestsOptionsSchema.partial().shape,
|
||||
}).parse(params);
|
||||
|
||||
const url = new URL(`https://api.github.com/repos/${owner}/${repo}/pulls`);
|
||||
|
||||
Object.entries(options).forEach(([key, value]) => {
|
||||
if (value !== undefined) {
|
||||
url.searchParams.append(key, value.toString());
|
||||
|
||||
Reference in New Issue
Block a user