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 { await this.ensureInitialized(); try { const params: Record = {}; 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 { 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 { await this.ensureInitialized(); try { const params: Record = {}; 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 { await this.ensureInitialized(); try { const params: Record = {}; 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 { await this.ensureInitialized(); try { const params: Record = {}; 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 { await this.ensureInitialized(); try { const params: Record = {}; 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { await this.ensureInitialized(); try { const body: Record = { 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 { await this.ensureInitialized(); try { const body: Record = {}; 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 { await this.ensureInitialized(); try { const body: Record = {}; 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 { 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 { 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 { 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 { 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 { 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)}`); } } }