feat: add PullRequestClient with read and write methods
This commit is contained in:
390
src/clients/pull-request-client.ts
Normal file
390
src/clients/pull-request-client.ts
Normal 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)}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user