feat: add PullRequestClient with read and write methods

This commit is contained in:
2026-05-20 19:04:08 +02:00
parent cf60d7a8fe
commit a0ab910137

View File

@@ -0,0 +1,390 @@
import { BaseClient, ClientOptions } from './base-client.js';
export interface CreatePROptions {
title: string;
source_branch: string;
destination_branch: string;
description?: string;
reviewers?: string[];
close_source_branch?: boolean;
}
export interface UpdatePROptions {
title?: string;
description?: string;
reviewers?: string[];
destination_branch?: string;
}
export interface MergePROptions {
merge_strategy?: 'merge_commit' | 'squash' | 'fast_forward';
commit_message?: string;
close_source_branch?: boolean;
}
export class PullRequestClient extends BaseClient {
constructor(options: ClientOptions = {}) {
super(options);
}
async listPullRequests(
workspace: string,
repoSlug: string,
options?: { state?: string; author?: string; reviewer?: string; since?: string },
): Promise<any[]> {
await this.ensureInitialized();
try {
const params: Record<string, any> = {};
if (options?.state) params.state = options.state;
if (options?.author) params.author = options.author;
if (options?.reviewer) params.reviewer = options.reviewer;
if (options?.since) params.since = options.since;
const response = await this.axiosInstance.get(
`/repositories/${workspace}/${repoSlug}/pullrequests`,
{ params },
);
return response.data.values || [];
} catch (error) {
throw new Error(`Failed to list pull requests: ${this.formatError(error)}`);
}
}
async getPullRequest(workspace: string, repoSlug: string, prId: number): Promise<any> {
await this.ensureInitialized();
try {
const response = await this.axiosInstance.get(
`/repositories/${workspace}/${repoSlug}/pullrequests/${prId}`,
);
return response.data;
} catch (error) {
throw new Error(`Failed to get pull request: ${this.formatError(error)}`);
}
}
async getPullRequestActivities(
workspace: string, repoSlug: string, prId: number,
options?: { limit?: number; start?: number },
): Promise<any> {
await this.ensureInitialized();
try {
const params: Record<string, any> = {};
if (options?.limit) params.limit = options.limit;
if (options?.start) params.start = options.start;
const response = await this.axiosInstance.get(
`/repositories/${workspace}/${repoSlug}/pullrequests/${prId}/activity`,
{ params },
);
return response.data;
} catch (error) {
throw new Error(`Failed to get activities: ${this.formatError(error)}`);
}
}
async getPullRequestChanges(
workspace: string, repoSlug: string, prId: number,
options?: { limit?: number; start?: number },
): Promise<any> {
await this.ensureInitialized();
try {
const params: Record<string, any> = {};
if (options?.limit) params.limit = options.limit;
if (options?.start) params.start = options.start;
const prResponse = await this.axiosInstance.get(
`/repositories/${workspace}/${repoSlug}/pullrequests/${prId}`,
);
const sourceHash = prResponse.data.source?.commit?.hash;
const destHash = prResponse.data.destination?.commit?.hash;
if (!sourceHash || !destHash) {
throw new Error('Could not determine source and destination commits');
}
const response = await this.axiosInstance.get(
`/repositories/${workspace}/${repoSlug}/diffstat/${sourceHash}..${destHash}`,
{ params },
);
return response.data;
} catch (error) {
throw new Error(`Failed to get changes: ${this.formatError(error)}`);
}
}
async getPullRequestCommits(
workspace: string, repoSlug: string, prId: number,
options?: { limit?: number; start?: number },
): Promise<any> {
await this.ensureInitialized();
try {
const params: Record<string, any> = {};
if (options?.limit) params.limit = options.limit;
if (options?.start) params.start = options.start;
const response = await this.axiosInstance.get(
`/repositories/${workspace}/${repoSlug}/pullrequests/${prId}/commits`,
{ params },
);
return response.data;
} catch (error) {
throw new Error(`Failed to get commits: ${this.formatError(error)}`);
}
}
async getPullRequestDiff(
workspace: string, repoSlug: string, prId: number,
options?: { context?: number; path?: string; whitespace?: string },
): Promise<any> {
await this.ensureInitialized();
try {
const params: Record<string, any> = {};
if (options?.context) params.context = options.context;
if (options?.whitespace) params.whitespace = options.whitespace;
if (options?.path) {
const response = await this.axiosInstance.get(
`/repositories/${workspace}/${repoSlug}/pullrequests/${prId}/diff/${options.path}`,
{ params, responseType: 'text' },
);
return response.data;
}
const prResponse = await this.axiosInstance.get(
`/repositories/${workspace}/${repoSlug}/pullrequests/${prId}`,
);
const sourceHash = prResponse.data.source?.commit?.hash;
const destHash = prResponse.data.destination?.commit?.hash;
if (!sourceHash || !destHash) {
throw new Error('Could not determine source and destination commits');
}
const response = await this.axiosInstance.get(
`/repositories/${workspace}/${repoSlug}/diff/${sourceHash}..${destHash}`,
{ params, responseType: 'text' },
);
return response.data;
} catch (error) {
throw new Error(`Failed to get diff: ${this.formatError(error)}`);
}
}
async getPullRequestPatch(workspace: string, repoSlug: string, prId: number): Promise<any> {
await this.ensureInitialized();
try {
const prResponse = await this.axiosInstance.get(
`/repositories/${workspace}/${repoSlug}/pullrequests/${prId}`,
);
const sourceHash = prResponse.data.source?.commit?.hash;
const destHash = prResponse.data.destination?.commit?.hash;
if (!sourceHash || !destHash) {
throw new Error('Could not determine source and destination commits');
}
const response = await this.axiosInstance.get(
`/repositories/${workspace}/${repoSlug}/diff/${sourceHash}..${destHash}`,
{ responseType: 'text' },
);
return { patch: response.data };
} catch (error) {
throw new Error(`Failed to get patch: ${this.formatError(error)}`);
}
}
async getPullRequestParticipants(workspace: string, repoSlug: string, prId: number): Promise<any> {
await this.ensureInitialized();
try {
const response = await this.axiosInstance.get(
`/repositories/${workspace}/${repoSlug}/pullrequests/${prId}`,
);
return response.data.participants || [];
} catch (error) {
throw new Error(`Failed to get participants: ${this.formatError(error)}`);
}
}
async getPullRequestReviewers(workspace: string, repoSlug: string, prId: number): Promise<any> {
await this.ensureInitialized();
try {
const response = await this.axiosInstance.get(
`/repositories/${workspace}/${repoSlug}/pullrequests/${prId}`,
);
return response.data.reviewers || [];
} catch (error) {
throw new Error(`Failed to get reviewers: ${this.formatError(error)}`);
}
}
async getPullRequestStatus(workspace: string, repoSlug: string, prId: number): Promise<any> {
await this.ensureInitialized();
try {
const response = await this.axiosInstance.get(
`/repositories/${workspace}/${repoSlug}/pullrequests/${prId}`,
);
const pr = response.data;
return {
id: pr.id, title: pr.title, state: pr.state, status: pr.status,
author: pr.author,
source_branch: pr.source?.branch?.name,
destination_branch: pr.destination?.branch?.name,
created_on: pr.created_on, updated_on: pr.updated_on,
closed_on: pr.closed_on, merge_commit: pr.merge_commit,
};
} catch (error) {
throw new Error(`Failed to get status: ${this.formatError(error)}`);
}
}
async getPullRequestTasks(workspace: string, repoSlug: string, prId: number): Promise<any> {
await this.ensureInitialized();
try {
const response = await this.axiosInstance.get(
`/repositories/${workspace}/${repoSlug}/pullrequests/${prId}/tasks`,
);
return response.data;
} catch (error) {
throw new Error(`Failed to get tasks: ${this.formatError(error)}`);
}
}
async getPullRequestTaskCount(workspace: string, repoSlug: string, prId: number): Promise<any> {
await this.ensureInitialized();
try {
const response = await this.axiosInstance.get(
`/repositories/${workspace}/${repoSlug}/pullrequests/${prId}/tasks`,
);
return { count: response.data.values?.length || 0 };
} catch (error) {
throw new Error(`Failed to get task count: ${this.formatError(error)}`);
}
}
async getFullPullRequest(workspace: string, repoSlug: string, prId: number): Promise<any> {
await this.ensureInitialized();
try {
const response = await this.axiosInstance.get(
`/repositories/${workspace}/${repoSlug}/pullrequests/${prId}`,
{ params: { fields: '+*' } },
);
return response.data;
} catch (error) {
throw new Error(`Failed to get full PR: ${this.formatError(error)}`);
}
}
async createPullRequest(workspace: string, repoSlug: string, opts: CreatePROptions): Promise<any> {
await this.ensureInitialized();
try {
const body: Record<string, any> = {
title: opts.title,
source: { branch: { name: opts.source_branch } },
destination: { branch: { name: opts.destination_branch } },
};
if (opts.description !== undefined) body.description = opts.description;
if (opts.close_source_branch !== undefined) body.close_source_branch = opts.close_source_branch;
if (opts.reviewers?.length) {
body.reviewers = opts.reviewers.map(r => ({ uuid: r }));
}
const response = await this.axiosInstance.post(
`/repositories/${workspace}/${repoSlug}/pullrequests`,
body,
);
return response.data;
} catch (error) {
throw new Error(`Failed to create pull request: ${this.formatError(error)}`);
}
}
async updatePullRequest(
workspace: string, repoSlug: string, prId: number, opts: UpdatePROptions,
): Promise<any> {
await this.ensureInitialized();
try {
const body: Record<string, any> = {};
if (opts.title !== undefined) body.title = opts.title;
if (opts.description !== undefined) body.description = opts.description;
if (opts.destination_branch !== undefined) {
body.destination = { branch: { name: opts.destination_branch } };
}
if (opts.reviewers !== undefined) {
body.reviewers = opts.reviewers.map(r => ({ uuid: r }));
}
const response = await this.axiosInstance.put(
`/repositories/${workspace}/${repoSlug}/pullrequests/${prId}`,
body,
);
return response.data;
} catch (error) {
throw new Error(`Failed to update pull request: ${this.formatError(error)}`);
}
}
async mergePullRequest(
workspace: string, repoSlug: string, prId: number, opts: MergePROptions = {},
): Promise<any> {
await this.ensureInitialized();
try {
const body: Record<string, any> = {};
if (opts.merge_strategy) body.merge_strategy = opts.merge_strategy;
if (opts.commit_message) body.message = opts.commit_message;
if (opts.close_source_branch !== undefined) body.close_source_branch = opts.close_source_branch;
const response = await this.axiosInstance.post(
`/repositories/${workspace}/${repoSlug}/pullrequests/${prId}/merge`,
body,
);
return response.data;
} catch (error) {
throw new Error(`Failed to merge pull request: ${this.formatError(error)}`);
}
}
async declinePullRequest(workspace: string, repoSlug: string, prId: number): Promise<any> {
await this.ensureInitialized();
try {
const response = await this.axiosInstance.post(
`/repositories/${workspace}/${repoSlug}/pullrequests/${prId}/decline`,
);
return response.data;
} catch (error) {
throw new Error(`Failed to decline pull request: ${this.formatError(error)}`);
}
}
async approvePullRequest(workspace: string, repoSlug: string, prId: number): Promise<any> {
await this.ensureInitialized();
try {
const response = await this.axiosInstance.post(
`/repositories/${workspace}/${repoSlug}/pullrequests/${prId}/approve`,
);
return response.data;
} catch (error) {
throw new Error(`Failed to approve pull request: ${this.formatError(error)}`);
}
}
async unapprovePullRequest(workspace: string, repoSlug: string, prId: number): Promise<any> {
await this.ensureInitialized();
try {
await this.axiosInstance.delete(
`/repositories/${workspace}/${repoSlug}/pullrequests/${prId}/approve`,
);
return { success: true };
} catch (error) {
throw new Error(`Failed to unapprove pull request: ${this.formatError(error)}`);
}
}
async requestChangesPullRequest(workspace: string, repoSlug: string, prId: number): Promise<any> {
await this.ensureInitialized();
try {
const response = await this.axiosInstance.post(
`/repositories/${workspace}/${repoSlug}/pullrequests/${prId}/request-changes`,
);
return response.data;
} catch (error) {
throw new Error(`Failed to request changes: ${this.formatError(error)}`);
}
}
async removeRequestChangesPullRequest(workspace: string, repoSlug: string, prId: number): Promise<any> {
await this.ensureInitialized();
try {
await this.axiosInstance.delete(
`/repositories/${workspace}/${repoSlug}/pullrequests/${prId}/request-changes`,
);
return { success: true };
} catch (error) {
throw new Error(`Failed to remove request-changes: ${this.formatError(error)}`);
}
}
}