@atlassian-dc-mcp/bitbucket 0.10.0 → 0.10.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +16 -0
- package/build/__tests__/bitbucket-service.test.js +79 -0
- package/build/__tests__/bitbucket-service.test.js.map +1 -1
- package/build/__tests__/inbox-pr-mapper.test.d.ts +2 -0
- package/build/__tests__/inbox-pr-mapper.test.d.ts.map +1 -0
- package/build/__tests__/inbox-pr-mapper.test.js +304 -0
- package/build/__tests__/inbox-pr-mapper.test.js.map +1 -0
- package/build/bitbucket-service.d.ts +11 -0
- package/build/bitbucket-service.d.ts.map +1 -1
- package/build/bitbucket-service.js +31 -0
- package/build/bitbucket-service.js.map +1 -1
- package/build/inbox-pr-mapper.d.ts +39 -0
- package/build/inbox-pr-mapper.d.ts.map +1 -0
- package/build/inbox-pr-mapper.js +76 -0
- package/build/inbox-pr-mapper.js.map +1 -0
- package/build/index.js +4 -0
- package/build/index.js.map +1 -1
- package/package.json +6 -6
- package/src/__tests__/bitbucket-service.test.ts +98 -0
- package/src/__tests__/inbox-pr-mapper.test.ts +347 -0
- package/src/bitbucket-service.ts +37 -0
- package/src/inbox-pr-mapper.ts +183 -0
- package/src/index.ts +10 -0
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
import { simplifyInboxPullRequests, SimplifiedInboxPRResponse } from '../inbox-pr-mapper.js';
|
|
2
|
+
|
|
3
|
+
describe('simplifyInboxPullRequests', () => {
|
|
4
|
+
const makePR = (overrides: Record<string, unknown> = {}) => ({
|
|
5
|
+
id: 101,
|
|
6
|
+
version: 0,
|
|
7
|
+
title: 'feat: Add user authentication module',
|
|
8
|
+
description: 'Implements OAuth2 login flow',
|
|
9
|
+
state: 'OPEN',
|
|
10
|
+
open: true,
|
|
11
|
+
closed: false,
|
|
12
|
+
draft: false,
|
|
13
|
+
createdDate: 1700000000000,
|
|
14
|
+
updatedDate: 1700001000000,
|
|
15
|
+
locked: false,
|
|
16
|
+
author: {
|
|
17
|
+
user: {
|
|
18
|
+
name: 'jsmith',
|
|
19
|
+
emailAddress: 'jsmith@example.com',
|
|
20
|
+
active: true,
|
|
21
|
+
displayName: 'John Smith',
|
|
22
|
+
id: 1001,
|
|
23
|
+
slug: 'jsmith',
|
|
24
|
+
type: 'NORMAL',
|
|
25
|
+
links: { self: [{ href: 'https://bitbucket.example.com/users/jsmith' }] },
|
|
26
|
+
},
|
|
27
|
+
role: 'AUTHOR',
|
|
28
|
+
approved: false,
|
|
29
|
+
status: 'UNAPPROVED',
|
|
30
|
+
},
|
|
31
|
+
fromRef: {
|
|
32
|
+
id: 'refs/heads/feat/user-auth',
|
|
33
|
+
displayId: 'feat/user-auth',
|
|
34
|
+
latestCommit: 'abc123def456abc123def456abc123def456abc1',
|
|
35
|
+
type: 'BRANCH',
|
|
36
|
+
repository: {
|
|
37
|
+
slug: 'myproject-api',
|
|
38
|
+
id: 100,
|
|
39
|
+
name: 'myproject-api',
|
|
40
|
+
hierarchyId: 'aabb11cc22dd33',
|
|
41
|
+
scmId: 'git',
|
|
42
|
+
state: 'AVAILABLE',
|
|
43
|
+
statusMessage: 'Available',
|
|
44
|
+
forkable: true,
|
|
45
|
+
project: {
|
|
46
|
+
key: 'PROJ',
|
|
47
|
+
id: 10,
|
|
48
|
+
name: 'Project Alpha',
|
|
49
|
+
public: false,
|
|
50
|
+
type: 'NORMAL',
|
|
51
|
+
links: { self: [{ href: 'https://bitbucket.example.com/projects/PROJ' }] },
|
|
52
|
+
},
|
|
53
|
+
public: false,
|
|
54
|
+
archived: false,
|
|
55
|
+
links: {
|
|
56
|
+
clone: [
|
|
57
|
+
{ href: 'ssh://git@bitbucket.example.com/proj/myproject-api.git', name: 'ssh' },
|
|
58
|
+
{ href: 'https://bitbucket.example.com/scm/proj/myproject-api.git', name: 'http' },
|
|
59
|
+
],
|
|
60
|
+
self: [{ href: 'https://bitbucket.example.com/projects/PROJ/repos/myproject-api/browse' }],
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
toRef: {
|
|
65
|
+
id: 'refs/heads/main',
|
|
66
|
+
displayId: 'main',
|
|
67
|
+
latestCommit: 'def456abc123def456abc123def456abc123def4',
|
|
68
|
+
type: 'BRANCH',
|
|
69
|
+
repository: {
|
|
70
|
+
slug: 'myproject-api',
|
|
71
|
+
id: 100,
|
|
72
|
+
name: 'myproject-api',
|
|
73
|
+
project: { key: 'PROJ', id: 10, name: 'Project Alpha' },
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
reviewers: [
|
|
77
|
+
{
|
|
78
|
+
user: { name: 'adoe', displayName: 'Alice Doe', emailAddress: 'adoe@example.com', id: 1002 },
|
|
79
|
+
role: 'REVIEWER',
|
|
80
|
+
approved: false,
|
|
81
|
+
status: 'UNAPPROVED',
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
user: { name: 'bjones', displayName: 'Bob Jones', emailAddress: 'bjones@example.com', id: 1003 },
|
|
85
|
+
lastReviewedCommit: 'abc123def456abc123def456abc123def456abc1',
|
|
86
|
+
role: 'REVIEWER',
|
|
87
|
+
approved: true,
|
|
88
|
+
status: 'APPROVED',
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
user: { name: 'clee', displayName: 'Carol Lee', emailAddress: 'clee@example.com', id: 1004 },
|
|
92
|
+
role: 'REVIEWER',
|
|
93
|
+
approved: false,
|
|
94
|
+
status: 'UNAPPROVED',
|
|
95
|
+
},
|
|
96
|
+
],
|
|
97
|
+
participants: [],
|
|
98
|
+
properties: {
|
|
99
|
+
mergeResult: { outcome: 'CLEAN', current: true },
|
|
100
|
+
resolvedTaskCount: 0,
|
|
101
|
+
commentCount: 3,
|
|
102
|
+
openTaskCount: 1,
|
|
103
|
+
},
|
|
104
|
+
links: {
|
|
105
|
+
self: [{ href: 'https://bitbucket.example.com/projects/PROJ/repos/myproject-api/pull-requests/101' }],
|
|
106
|
+
},
|
|
107
|
+
...overrides,
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
const makeResponse = (prs: unknown[] = [makePR()], overrides: Record<string, unknown> = {}) => ({
|
|
111
|
+
values: prs,
|
|
112
|
+
size: prs.length,
|
|
113
|
+
isLastPage: true,
|
|
114
|
+
start: 0,
|
|
115
|
+
limit: 25,
|
|
116
|
+
...overrides,
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it('should simplify a valid inbox response with real API structure', () => {
|
|
120
|
+
const response = makeResponse();
|
|
121
|
+
const result = simplifyInboxPullRequests(response) as SimplifiedInboxPRResponse;
|
|
122
|
+
|
|
123
|
+
expect(result.pullRequests).toHaveLength(1);
|
|
124
|
+
expect(result.pullRequests[0]).toEqual({
|
|
125
|
+
id: 101,
|
|
126
|
+
title: 'feat: Add user authentication module',
|
|
127
|
+
description: 'Implements OAuth2 login flow',
|
|
128
|
+
state: 'OPEN',
|
|
129
|
+
draft: false,
|
|
130
|
+
createdDate: 1700000000000,
|
|
131
|
+
updatedDate: 1700001000000,
|
|
132
|
+
link: 'https://bitbucket.example.com/projects/PROJ/repos/myproject-api/pull-requests/101',
|
|
133
|
+
author: { name: 'jsmith', displayName: 'John Smith' },
|
|
134
|
+
fromRef: 'feat/user-auth',
|
|
135
|
+
toRef: 'main',
|
|
136
|
+
repository: { slug: 'myproject-api', projectKey: 'PROJ' },
|
|
137
|
+
reviewers: [
|
|
138
|
+
{ name: 'adoe', approved: false, status: 'UNAPPROVED' },
|
|
139
|
+
{ name: 'bjones', approved: true, status: 'APPROVED' },
|
|
140
|
+
{ name: 'clee', approved: false, status: 'UNAPPROVED' },
|
|
141
|
+
],
|
|
142
|
+
commentCount: 3,
|
|
143
|
+
openTaskCount: 1,
|
|
144
|
+
});
|
|
145
|
+
expect(result.summary).toEqual({
|
|
146
|
+
totalCount: 1,
|
|
147
|
+
byRepository: { 'PROJ/myproject-api': 1 },
|
|
148
|
+
});
|
|
149
|
+
expect(result.isLastPage).toBe(true);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it('should extract the PR link from links.self[0].href', () => {
|
|
153
|
+
const response = makeResponse();
|
|
154
|
+
const result = simplifyInboxPullRequests(response) as SimplifiedInboxPRResponse;
|
|
155
|
+
|
|
156
|
+
expect(result.pullRequests[0].link).toBe(
|
|
157
|
+
'https://bitbucket.example.com/projects/PROJ/repos/myproject-api/pull-requests/101'
|
|
158
|
+
);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it('should omit link when links.self is not present', () => {
|
|
162
|
+
const pr = makePR({ links: undefined });
|
|
163
|
+
const response = makeResponse([pr]);
|
|
164
|
+
const result = simplifyInboxPullRequests(response) as SimplifiedInboxPRResponse;
|
|
165
|
+
|
|
166
|
+
expect(result.pullRequests[0].link).toBeUndefined();
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it('should include commentCount and openTaskCount from properties', () => {
|
|
170
|
+
const pr = makePR({
|
|
171
|
+
properties: { commentCount: 5, openTaskCount: 2, resolvedTaskCount: 1 },
|
|
172
|
+
});
|
|
173
|
+
const response = makeResponse([pr]);
|
|
174
|
+
const result = simplifyInboxPullRequests(response) as SimplifiedInboxPRResponse;
|
|
175
|
+
|
|
176
|
+
expect(result.pullRequests[0].commentCount).toBe(5);
|
|
177
|
+
expect(result.pullRequests[0].openTaskCount).toBe(2);
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it('should default commentCount and openTaskCount to 0 when properties missing', () => {
|
|
181
|
+
const pr = makePR({ properties: undefined });
|
|
182
|
+
const response = makeResponse([pr]);
|
|
183
|
+
const result = simplifyInboxPullRequests(response) as SimplifiedInboxPRResponse;
|
|
184
|
+
|
|
185
|
+
expect(result.pullRequests[0].commentCount).toBe(0);
|
|
186
|
+
expect(result.pullRequests[0].openTaskCount).toBe(0);
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
it('should include description when present', () => {
|
|
190
|
+
const response = makeResponse();
|
|
191
|
+
const result = simplifyInboxPullRequests(response) as SimplifiedInboxPRResponse;
|
|
192
|
+
|
|
193
|
+
expect(result.pullRequests[0].description).toBe('Implements OAuth2 login flow');
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
it('should omit description when not present', () => {
|
|
197
|
+
const pr = makePR({ description: undefined });
|
|
198
|
+
const response = makeResponse([pr]);
|
|
199
|
+
const result = simplifyInboxPullRequests(response) as SimplifiedInboxPRResponse;
|
|
200
|
+
|
|
201
|
+
expect(result.pullRequests[0].description).toBeUndefined();
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it('should include draft status', () => {
|
|
205
|
+
const pr = makePR({ draft: true });
|
|
206
|
+
const response = makeResponse([pr]);
|
|
207
|
+
const result = simplifyInboxPullRequests(response) as SimplifiedInboxPRResponse;
|
|
208
|
+
|
|
209
|
+
expect(result.pullRequests[0].draft).toBe(true);
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
it('should default draft to false when not present', () => {
|
|
213
|
+
const pr = makePR({ draft: undefined });
|
|
214
|
+
const response = makeResponse([pr]);
|
|
215
|
+
const result = simplifyInboxPullRequests(response) as SimplifiedInboxPRResponse;
|
|
216
|
+
|
|
217
|
+
expect(result.pullRequests[0].draft).toBe(false);
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
it('should handle multiple PRs from different repositories', () => {
|
|
221
|
+
const pr1 = makePR();
|
|
222
|
+
const pr2 = makePR({
|
|
223
|
+
id: 200,
|
|
224
|
+
title: 'fix: Resolve cache invalidation bug',
|
|
225
|
+
fromRef: {
|
|
226
|
+
id: 'refs/heads/fix/cache-bug',
|
|
227
|
+
displayId: 'fix/cache-bug',
|
|
228
|
+
repository: { slug: 'frontend-app', project: { key: 'WEB' } },
|
|
229
|
+
},
|
|
230
|
+
toRef: {
|
|
231
|
+
id: 'refs/heads/main',
|
|
232
|
+
displayId: 'main',
|
|
233
|
+
repository: { slug: 'frontend-app', project: { key: 'WEB' } },
|
|
234
|
+
},
|
|
235
|
+
links: {
|
|
236
|
+
self: [{ href: 'https://bitbucket.example.com/projects/WEB/repos/frontend-app/pull-requests/200' }],
|
|
237
|
+
},
|
|
238
|
+
});
|
|
239
|
+
const pr3 = makePR({ id: 102, title: 'chore: Update dependencies' });
|
|
240
|
+
|
|
241
|
+
const response = makeResponse([pr1, pr2, pr3]);
|
|
242
|
+
const result = simplifyInboxPullRequests(response) as SimplifiedInboxPRResponse;
|
|
243
|
+
|
|
244
|
+
expect(result.pullRequests).toHaveLength(3);
|
|
245
|
+
expect(result.summary.totalCount).toBe(3);
|
|
246
|
+
expect(result.summary.byRepository).toEqual({
|
|
247
|
+
'PROJ/myproject-api': 2,
|
|
248
|
+
'WEB/frontend-app': 1,
|
|
249
|
+
});
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
it('should handle empty values array', () => {
|
|
253
|
+
const response = makeResponse([]);
|
|
254
|
+
const result = simplifyInboxPullRequests(response) as SimplifiedInboxPRResponse;
|
|
255
|
+
|
|
256
|
+
expect(result.pullRequests).toHaveLength(0);
|
|
257
|
+
expect(result.summary.totalCount).toBe(0);
|
|
258
|
+
expect(result.summary.byRepository).toEqual({});
|
|
259
|
+
expect(result.isLastPage).toBe(true);
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
it('should include nextPageStart when not the last page', () => {
|
|
263
|
+
const response = makeResponse([makePR()], {
|
|
264
|
+
isLastPage: false,
|
|
265
|
+
nextPageStart: 25,
|
|
266
|
+
});
|
|
267
|
+
const result = simplifyInboxPullRequests(response) as SimplifiedInboxPRResponse;
|
|
268
|
+
|
|
269
|
+
expect(result.isLastPage).toBe(false);
|
|
270
|
+
expect(result.nextPageStart).toBe(25);
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
it('should not include nextPageStart when it is the last page', () => {
|
|
274
|
+
const response = makeResponse([makePR()]);
|
|
275
|
+
const result = simplifyInboxPullRequests(response) as SimplifiedInboxPRResponse;
|
|
276
|
+
|
|
277
|
+
expect(result.isLastPage).toBe(true);
|
|
278
|
+
expect(result.nextPageStart).toBeUndefined();
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
it('should handle PR without author', () => {
|
|
282
|
+
const pr = makePR({ author: undefined });
|
|
283
|
+
const response = makeResponse([pr]);
|
|
284
|
+
const result = simplifyInboxPullRequests(response) as SimplifiedInboxPRResponse;
|
|
285
|
+
|
|
286
|
+
expect(result.pullRequests[0].author).toBeUndefined();
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
it('should handle PR without reviewers', () => {
|
|
290
|
+
const pr = makePR({ reviewers: undefined });
|
|
291
|
+
const response = makeResponse([pr]);
|
|
292
|
+
const result = simplifyInboxPullRequests(response) as SimplifiedInboxPRResponse;
|
|
293
|
+
|
|
294
|
+
expect(result.pullRequests[0].reviewers).toEqual([]);
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
it('should return original response for invalid input', () => {
|
|
298
|
+
const invalidResponse = { foo: 'bar' };
|
|
299
|
+
const result = simplifyInboxPullRequests(invalidResponse);
|
|
300
|
+
|
|
301
|
+
expect(result).toBe(invalidResponse);
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
it('should return original response for null input', () => {
|
|
305
|
+
const result = simplifyInboxPullRequests(null);
|
|
306
|
+
expect(result).toBeNull();
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
it('should return original response when values contain no valid PRs', () => {
|
|
310
|
+
const response = makeResponse([{ invalid: true }, { alsoInvalid: true }]);
|
|
311
|
+
const result = simplifyInboxPullRequests(response);
|
|
312
|
+
|
|
313
|
+
expect(result).toBe(response);
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
it('should use ref id as fallback when displayId is missing', () => {
|
|
317
|
+
const pr = makePR({
|
|
318
|
+
fromRef: {
|
|
319
|
+
id: 'refs/heads/feat/user-auth',
|
|
320
|
+
repository: { slug: 'myproject-api', project: { key: 'PROJ' } },
|
|
321
|
+
},
|
|
322
|
+
toRef: {
|
|
323
|
+
id: 'refs/heads/main',
|
|
324
|
+
repository: { slug: 'myproject-api', project: { key: 'PROJ' } },
|
|
325
|
+
},
|
|
326
|
+
});
|
|
327
|
+
const response = makeResponse([pr]);
|
|
328
|
+
const result = simplifyInboxPullRequests(response) as SimplifiedInboxPRResponse;
|
|
329
|
+
|
|
330
|
+
expect(result.pullRequests[0].fromRef).toBe('refs/heads/feat/user-auth');
|
|
331
|
+
expect(result.pullRequests[0].toRef).toBe('refs/heads/main');
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
it('should strip extra fields from the real API response', () => {
|
|
335
|
+
const response = makeResponse();
|
|
336
|
+
const result = simplifyInboxPullRequests(response) as SimplifiedInboxPRResponse;
|
|
337
|
+
const pr = result.pullRequests[0];
|
|
338
|
+
|
|
339
|
+
expect(pr).not.toHaveProperty('version');
|
|
340
|
+
expect(pr).not.toHaveProperty('open');
|
|
341
|
+
expect(pr).not.toHaveProperty('closed');
|
|
342
|
+
expect(pr).not.toHaveProperty('locked');
|
|
343
|
+
expect(pr).not.toHaveProperty('participants');
|
|
344
|
+
expect(pr).not.toHaveProperty('properties');
|
|
345
|
+
expect(pr).not.toHaveProperty('links');
|
|
346
|
+
});
|
|
347
|
+
});
|
package/src/bitbucket-service.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { request as __request } from './bitbucket-client/core/request.js';
|
|
|
4
4
|
import { handleApiOperation } from '@atlassian-dc-mcp/common';
|
|
5
5
|
import { simplifyBitbucketPRComments } from './pr-comment-mapper.js';
|
|
6
6
|
import { simplifyBitbucketPRChanges } from './pr-changes-mapper.js';
|
|
7
|
+
import { simplifyInboxPullRequests } from './inbox-pr-mapper.js';
|
|
7
8
|
|
|
8
9
|
export class BitbucketService {
|
|
9
10
|
constructor(host: string, token: string, fullBaseUrl?: string) {
|
|
@@ -496,6 +497,38 @@ export class BitbucketService {
|
|
|
496
497
|
);
|
|
497
498
|
}
|
|
498
499
|
|
|
500
|
+
/**
|
|
501
|
+
* Get pull requests from the authenticated user's inbox (PRs awaiting review)
|
|
502
|
+
* @param start Optional pagination start
|
|
503
|
+
* @param limit Optional pagination limit (default: 25)
|
|
504
|
+
* @returns Promise with inbox pull requests data
|
|
505
|
+
*/
|
|
506
|
+
async getInboxPullRequests(start?: number, limit: number = 25) {
|
|
507
|
+
const result = await handleApiOperation(
|
|
508
|
+
() => __request(OpenAPI, {
|
|
509
|
+
method: 'GET',
|
|
510
|
+
url: '/api/latest/inbox/pull-requests',
|
|
511
|
+
query: {
|
|
512
|
+
'start': start,
|
|
513
|
+
'limit': limit,
|
|
514
|
+
},
|
|
515
|
+
errors: {
|
|
516
|
+
401: 'The currently authenticated user is not permitted to access the inbox.',
|
|
517
|
+
},
|
|
518
|
+
}),
|
|
519
|
+
'Error fetching inbox pull requests'
|
|
520
|
+
);
|
|
521
|
+
|
|
522
|
+
if (result.success && result.data) {
|
|
523
|
+
return {
|
|
524
|
+
success: true,
|
|
525
|
+
data: simplifyInboxPullRequests(result.data),
|
|
526
|
+
};
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
return result;
|
|
530
|
+
}
|
|
531
|
+
|
|
499
532
|
static validateConfig(): string[] {
|
|
500
533
|
// Check for BITBUCKET_HOST or its alternative BITBUCKET_API_BASE_PATH
|
|
501
534
|
const requiredEnvVars = ['BITBUCKET_API_TOKEN'] as const;
|
|
@@ -621,5 +654,9 @@ export const bitbucketToolSchemas = {
|
|
|
621
654
|
targetRefId: z.string().describe("The ID of the target ref (e.g., 'refs/heads/main')"),
|
|
622
655
|
sourceRepoId: z.string().optional().describe("Optional ID of the repository in which the source ref exists"),
|
|
623
656
|
targetRepoId: z.string().optional().describe("Optional ID of the repository in which the target ref exists")
|
|
657
|
+
},
|
|
658
|
+
getInboxPullRequests: {
|
|
659
|
+
start: z.number().optional().describe("Start number for the page (inclusive). If not passed, first page is assumed"),
|
|
660
|
+
limit: z.number().optional().default(25).describe("Number of items to return. If not passed, a page size of 25 is used")
|
|
624
661
|
}
|
|
625
662
|
};
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
interface InboxPRUser {
|
|
2
|
+
name: string;
|
|
3
|
+
emailAddress?: string;
|
|
4
|
+
displayName?: string;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
interface InboxPRRef {
|
|
8
|
+
id: string;
|
|
9
|
+
displayId: string;
|
|
10
|
+
latestCommit?: string;
|
|
11
|
+
repository?: {
|
|
12
|
+
slug: string;
|
|
13
|
+
project?: {
|
|
14
|
+
key: string;
|
|
15
|
+
};
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
interface InboxPRReviewer {
|
|
20
|
+
user: InboxPRUser;
|
|
21
|
+
approved: boolean;
|
|
22
|
+
status: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
interface InboxPullRequest {
|
|
26
|
+
id: number;
|
|
27
|
+
title: string;
|
|
28
|
+
description?: string;
|
|
29
|
+
state: string;
|
|
30
|
+
draft?: boolean;
|
|
31
|
+
createdDate: number;
|
|
32
|
+
updatedDate: number;
|
|
33
|
+
author?: {
|
|
34
|
+
user: InboxPRUser;
|
|
35
|
+
};
|
|
36
|
+
fromRef: InboxPRRef;
|
|
37
|
+
toRef: InboxPRRef;
|
|
38
|
+
reviewers?: InboxPRReviewer[];
|
|
39
|
+
properties?: {
|
|
40
|
+
commentCount?: number;
|
|
41
|
+
openTaskCount?: number;
|
|
42
|
+
};
|
|
43
|
+
links?: {
|
|
44
|
+
self?: { href: string }[];
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
interface InboxPRResponse {
|
|
49
|
+
values: InboxPullRequest[];
|
|
50
|
+
size: number;
|
|
51
|
+
isLastPage: boolean;
|
|
52
|
+
start: number;
|
|
53
|
+
limit: number;
|
|
54
|
+
nextPageStart?: number;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Simplified interfaces
|
|
58
|
+
interface SimplifiedInboxPR {
|
|
59
|
+
id: number;
|
|
60
|
+
title: string;
|
|
61
|
+
description?: string;
|
|
62
|
+
state: string;
|
|
63
|
+
draft: boolean;
|
|
64
|
+
createdDate: number;
|
|
65
|
+
updatedDate: number;
|
|
66
|
+
link?: string;
|
|
67
|
+
author?: {
|
|
68
|
+
name: string;
|
|
69
|
+
displayName?: string;
|
|
70
|
+
};
|
|
71
|
+
fromRef: string;
|
|
72
|
+
toRef: string;
|
|
73
|
+
repository: {
|
|
74
|
+
slug: string;
|
|
75
|
+
projectKey: string;
|
|
76
|
+
};
|
|
77
|
+
reviewers: {
|
|
78
|
+
name: string;
|
|
79
|
+
approved: boolean;
|
|
80
|
+
status: string;
|
|
81
|
+
}[];
|
|
82
|
+
commentCount: number;
|
|
83
|
+
openTaskCount: number;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export interface SimplifiedInboxPRResponse {
|
|
87
|
+
pullRequests: SimplifiedInboxPR[];
|
|
88
|
+
summary: {
|
|
89
|
+
totalCount: number;
|
|
90
|
+
byRepository: Record<string, number>;
|
|
91
|
+
};
|
|
92
|
+
isLastPage: boolean;
|
|
93
|
+
nextPageStart?: number;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Type guards
|
|
97
|
+
function isInboxPRResponse(obj: unknown): obj is InboxPRResponse {
|
|
98
|
+
return (
|
|
99
|
+
typeof obj === 'object' &&
|
|
100
|
+
obj !== null &&
|
|
101
|
+
Array.isArray((obj as InboxPRResponse).values) &&
|
|
102
|
+
typeof (obj as InboxPRResponse).isLastPage === 'boolean'
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function isInboxPullRequest(obj: unknown): obj is InboxPullRequest {
|
|
107
|
+
return (
|
|
108
|
+
typeof obj === 'object' &&
|
|
109
|
+
obj !== null &&
|
|
110
|
+
typeof (obj as InboxPullRequest).id === 'number' &&
|
|
111
|
+
typeof (obj as InboxPullRequest).title === 'string' &&
|
|
112
|
+
typeof (obj as InboxPullRequest).state === 'string'
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function simplifyInboxPR(pr: InboxPullRequest): SimplifiedInboxPR {
|
|
117
|
+
const repo = pr.toRef?.repository ?? pr.fromRef?.repository;
|
|
118
|
+
const link = pr.links?.self?.[0]?.href;
|
|
119
|
+
|
|
120
|
+
return {
|
|
121
|
+
id: pr.id,
|
|
122
|
+
title: pr.title,
|
|
123
|
+
...(pr.description !== undefined && { description: pr.description }),
|
|
124
|
+
state: pr.state,
|
|
125
|
+
draft: pr.draft ?? false,
|
|
126
|
+
createdDate: pr.createdDate,
|
|
127
|
+
updatedDate: pr.updatedDate,
|
|
128
|
+
...(link && { link }),
|
|
129
|
+
...(pr.author?.user && {
|
|
130
|
+
author: {
|
|
131
|
+
name: pr.author.user.name,
|
|
132
|
+
...(pr.author.user.displayName && { displayName: pr.author.user.displayName }),
|
|
133
|
+
},
|
|
134
|
+
}),
|
|
135
|
+
fromRef: pr.fromRef?.displayId ?? pr.fromRef?.id,
|
|
136
|
+
toRef: pr.toRef?.displayId ?? pr.toRef?.id,
|
|
137
|
+
repository: {
|
|
138
|
+
slug: repo?.slug ?? 'unknown',
|
|
139
|
+
projectKey: repo?.project?.key ?? 'unknown',
|
|
140
|
+
},
|
|
141
|
+
reviewers: (pr.reviewers ?? []).map(r => ({
|
|
142
|
+
name: r.user.name,
|
|
143
|
+
approved: r.approved,
|
|
144
|
+
status: r.status,
|
|
145
|
+
})),
|
|
146
|
+
commentCount: pr.properties?.commentCount ?? 0,
|
|
147
|
+
openTaskCount: pr.properties?.openTaskCount ?? 0,
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export function simplifyInboxPullRequests(response: unknown): SimplifiedInboxPRResponse | unknown {
|
|
152
|
+
if (!isInboxPRResponse(response)) {
|
|
153
|
+
return response;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const pullRequests: SimplifiedInboxPR[] = [];
|
|
157
|
+
|
|
158
|
+
for (const pr of response.values) {
|
|
159
|
+
if (isInboxPullRequest(pr)) {
|
|
160
|
+
pullRequests.push(simplifyInboxPR(pr));
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (pullRequests.length === 0 && response.values.length > 0) {
|
|
165
|
+
return response;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const byRepository: Record<string, number> = {};
|
|
169
|
+
for (const pr of pullRequests) {
|
|
170
|
+
const repoKey = `${pr.repository.projectKey}/${pr.repository.slug}`;
|
|
171
|
+
byRepository[repoKey] = (byRepository[repoKey] ?? 0) + 1;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return {
|
|
175
|
+
pullRequests,
|
|
176
|
+
summary: {
|
|
177
|
+
totalCount: pullRequests.length,
|
|
178
|
+
byRepository,
|
|
179
|
+
},
|
|
180
|
+
isLastPage: response.isLastPage,
|
|
181
|
+
...(response.nextPageStart !== undefined && { nextPageStart: response.nextPageStart }),
|
|
182
|
+
};
|
|
183
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -163,4 +163,14 @@ server.tool(
|
|
|
163
163
|
}
|
|
164
164
|
);
|
|
165
165
|
|
|
166
|
+
server.tool(
|
|
167
|
+
"bitbucket_getInboxPullRequests",
|
|
168
|
+
"Get pull requests from the authenticated user's inbox that need their review. Returns PRs across all repositories where the user is a reviewer.",
|
|
169
|
+
bitbucketToolSchemas.getInboxPullRequests,
|
|
170
|
+
async ({ start, limit }) => {
|
|
171
|
+
const result = await bitbucketService.getInboxPullRequests(start, limit);
|
|
172
|
+
return formatToolResponse(result);
|
|
173
|
+
}
|
|
174
|
+
);
|
|
175
|
+
|
|
166
176
|
await connectServer(server);
|