@stubbedev/atlassian-mcp 0.2.7 → 0.2.8
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/dist/bitbucket.js +15 -63
- package/package.json +1 -1
package/dist/bitbucket.js
CHANGED
|
@@ -183,7 +183,7 @@ function validateCommentText(textValue) {
|
|
|
183
183
|
export class BitbucketClient {
|
|
184
184
|
baseUrl;
|
|
185
185
|
headers;
|
|
186
|
-
|
|
186
|
+
currentUsernameCache;
|
|
187
187
|
constructor(baseUrl, token) {
|
|
188
188
|
this.baseUrl = baseUrl.replace(/\/$/, '');
|
|
189
189
|
this.headers = {
|
|
@@ -192,6 +192,18 @@ export class BitbucketClient {
|
|
|
192
192
|
Accept: 'application/json',
|
|
193
193
|
};
|
|
194
194
|
}
|
|
195
|
+
/** Returns the slug/username of the authenticated user via the X-AUSERNAME response header. */
|
|
196
|
+
async getCurrentUsername() {
|
|
197
|
+
if (this.currentUsernameCache)
|
|
198
|
+
return this.currentUsernameCache;
|
|
199
|
+
const url = `${this.baseUrl}/rest/api/1.0/application-properties`;
|
|
200
|
+
const res = await fetch(url, { method: 'GET', headers: this.headers });
|
|
201
|
+
const username = res.headers.get('X-AUSERNAME');
|
|
202
|
+
if (!username)
|
|
203
|
+
throw new Error('Could not determine current Bitbucket user. Check token permissions.');
|
|
204
|
+
this.currentUsernameCache = username;
|
|
205
|
+
return username;
|
|
206
|
+
}
|
|
195
207
|
/** Returns a URL-safe `/projects/.../repos/...` prefix for REST paths. */
|
|
196
208
|
rp(projectKey, repoSlug) {
|
|
197
209
|
return `/projects/${encodeURIComponent(projectKey)}/repos/${encodeURIComponent(repoSlug)}`;
|
|
@@ -278,38 +290,6 @@ export class BitbucketClient {
|
|
|
278
290
|
}
|
|
279
291
|
return res.status === 204 ? null : res.json();
|
|
280
292
|
}
|
|
281
|
-
normalizeIdentity(value) {
|
|
282
|
-
return (value ?? '').trim().toLowerCase();
|
|
283
|
-
}
|
|
284
|
-
async getCurrentUser() {
|
|
285
|
-
if (this.currentUserCache)
|
|
286
|
-
return this.currentUserCache;
|
|
287
|
-
const me = await this.request('GET', '/users/~self');
|
|
288
|
-
if (!me) {
|
|
289
|
-
throw new Error('Could not determine current Bitbucket user identity.');
|
|
290
|
-
}
|
|
291
|
-
this.currentUserCache = me;
|
|
292
|
-
return me;
|
|
293
|
-
}
|
|
294
|
-
async assertOwnComment(comment) {
|
|
295
|
-
const me = await this.getCurrentUser();
|
|
296
|
-
const commentAuthorName = this.normalizeIdentity(comment.author?.name);
|
|
297
|
-
const commentAuthorDisplayName = this.normalizeIdentity(comment.author?.displayName);
|
|
298
|
-
const meName = this.normalizeIdentity(me.name);
|
|
299
|
-
const meSlug = this.normalizeIdentity(me.slug);
|
|
300
|
-
const meDisplayName = this.normalizeIdentity(me.displayName);
|
|
301
|
-
const hasStrongCommentIdentity = commentAuthorName.length > 0;
|
|
302
|
-
const hasStrongUserIdentity = meName.length > 0 || meSlug.length > 0;
|
|
303
|
-
const matchesByName = commentAuthorName.length > 0 && (commentAuthorName === meName || commentAuthorName === meSlug);
|
|
304
|
-
const matchesByDisplayNameFallback = !hasStrongCommentIdentity
|
|
305
|
-
&& !hasStrongUserIdentity
|
|
306
|
-
&& commentAuthorDisplayName.length > 0
|
|
307
|
-
&& commentAuthorDisplayName === meDisplayName;
|
|
308
|
-
if (matchesByName || matchesByDisplayNameFallback) {
|
|
309
|
-
return;
|
|
310
|
-
}
|
|
311
|
-
throw new Error(`You can only edit your own Bitbucket comments. Comment #${comment.id} is authored by ${comment.author?.displayName ?? comment.author?.name ?? 'another user'}.`);
|
|
312
|
-
}
|
|
313
293
|
/** Returns true if the given remote URL belongs to this Bitbucket instance. */
|
|
314
294
|
isRemoteForThisInstance(remoteUrl) {
|
|
315
295
|
return this.remoteMatchesInstance(remoteUrl);
|
|
@@ -367,10 +347,7 @@ export class BitbucketClient {
|
|
|
367
347
|
}
|
|
368
348
|
async myPrs(args) {
|
|
369
349
|
const { limit = 25, start = 0, role } = args;
|
|
370
|
-
const
|
|
371
|
-
const userSlug = me.slug ?? me.name;
|
|
372
|
-
if (!userSlug)
|
|
373
|
-
throw new Error('Could not determine your Bitbucket user slug. Check token permissions.');
|
|
350
|
+
const userSlug = await this.getCurrentUsername();
|
|
374
351
|
const qs = new URLSearchParams({ limit: String(limit), start: String(start), state: 'OPEN' });
|
|
375
352
|
if (role)
|
|
376
353
|
qs.set('role', role.toUpperCase());
|
|
@@ -424,28 +401,12 @@ export class BitbucketClient {
|
|
|
424
401
|
throw new Error(`No open PR found for branch "${branchDisplayId(branch)}".`);
|
|
425
402
|
prId = found.id;
|
|
426
403
|
}
|
|
427
|
-
const
|
|
428
|
-
this.request('GET', `${this.rp(projectKey, repoSlug)}/pull-requests/${prId}`),
|
|
429
|
-
this.getCurrentUser().catch(() => null),
|
|
430
|
-
]);
|
|
404
|
+
const pr = await this.request('GET', `${this.rp(projectKey, repoSlug)}/pull-requests/${prId}`);
|
|
431
405
|
if (!pr)
|
|
432
406
|
return text('Pull request not found.');
|
|
433
407
|
const sections = [];
|
|
434
408
|
const reviewers = pr.reviewers.map((r) => `${r.user.displayName}${r.approved ? ' ✓' : ''}`).join(', ');
|
|
435
409
|
const url = pr.links?.self?.[0]?.href;
|
|
436
|
-
// Prefer slug (canonical unique identifier), fall back to username (name field).
|
|
437
|
-
// Display name is deliberately excluded — it is not unique.
|
|
438
|
-
const meSlug = this.normalizeIdentity(me?.slug);
|
|
439
|
-
const meName = this.normalizeIdentity(me?.name);
|
|
440
|
-
const authorSlug = this.normalizeIdentity(pr.author.user.slug);
|
|
441
|
-
const authorName = this.normalizeIdentity(pr.author.user.name);
|
|
442
|
-
const isAuthor = me
|
|
443
|
-
? (meSlug && authorSlug ? meSlug === authorSlug : false) ||
|
|
444
|
-
(meName && authorName ? meName === authorName : false)
|
|
445
|
-
: false;
|
|
446
|
-
const viewingAs = me
|
|
447
|
-
? `Viewing as: ${me.displayName} (${isAuthor ? 'you are the author' : 'you are a reviewer'})`
|
|
448
|
-
: '';
|
|
449
410
|
const header = [
|
|
450
411
|
`PR #${pr.id}: ${pr.title}`,
|
|
451
412
|
`State: ${pr.state}`,
|
|
@@ -453,7 +414,6 @@ export class BitbucketClient {
|
|
|
453
414
|
`Branch: ${pr.fromRef.displayId} → ${pr.toRef.displayId}`,
|
|
454
415
|
`Reviewers: ${reviewers || 'None'}`,
|
|
455
416
|
url ? `URL: ${url}` : '',
|
|
456
|
-
viewingAs,
|
|
457
417
|
'',
|
|
458
418
|
'Description:',
|
|
459
419
|
pr.description ?? '(no description)',
|
|
@@ -918,13 +878,6 @@ export class BitbucketClient {
|
|
|
918
878
|
if (!current)
|
|
919
879
|
throw new Error(`Comment #${args.commentId} not found.`);
|
|
920
880
|
const currentSeverity = current.severity ?? 'NORMAL';
|
|
921
|
-
const severityIsChanging = args.severity !== undefined && args.severity !== currentSeverity;
|
|
922
|
-
const isResolutionOnlyUpdate = (args.state !== undefined || args.threadResolved !== undefined)
|
|
923
|
-
&& args.text === undefined
|
|
924
|
-
&& !severityIsChanging;
|
|
925
|
-
if (!isResolutionOnlyUpdate) {
|
|
926
|
-
await this.assertOwnComment(current);
|
|
927
|
-
}
|
|
928
881
|
const targetSeverity = args.severity ?? currentSeverity;
|
|
929
882
|
if (args.state && targetSeverity !== 'BLOCKER') {
|
|
930
883
|
throw new Error('state is only supported for BLOCKER comments (tasks). Use threadResolved for normal comment threads.');
|
|
@@ -974,7 +927,6 @@ export class BitbucketClient {
|
|
|
974
927
|
const current = await this.request('GET', `${this.rp(projectKey, repoSlug)}/pull-requests/${args.prId}/comments/${args.commentId}`);
|
|
975
928
|
if (!current)
|
|
976
929
|
throw new Error(`Comment #${args.commentId} not found.`);
|
|
977
|
-
await this.assertOwnComment(current);
|
|
978
930
|
const commentPath = current.severity === 'BLOCKER'
|
|
979
931
|
? `${this.rp(projectKey, repoSlug)}/pull-requests/${args.prId}/blocker-comments/${args.commentId}`
|
|
980
932
|
: `${this.rp(projectKey, repoSlug)}/pull-requests/${args.prId}/comments/${args.commentId}`;
|