@atlassian-dc-mcp/bitbucket 0.25.0 → 0.25.1
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 +8 -0
- package/build/__tests__/bitbucket-service.test.js +86 -16
- package/build/__tests__/bitbucket-service.test.js.map +1 -1
- package/build/bitbucket-response-mapper.js +3 -3
- package/build/bitbucket-response-mapper.js.map +1 -1
- package/build/bitbucket-service.d.ts +7 -1
- package/build/bitbucket-service.d.ts.map +1 -1
- package/build/bitbucket-service.js +75 -22
- package/build/bitbucket-service.js.map +1 -1
- package/build/pr-comment-mapper.d.ts +2 -0
- package/build/pr-comment-mapper.d.ts.map +1 -1
- package/build/pr-comment-mapper.js +7 -1
- package/build/pr-comment-mapper.js.map +1 -1
- package/package.json +3 -3
- package/src/__tests__/bitbucket-service.test.ts +128 -16
- package/src/bitbucket-response-mapper.ts +3 -3
- package/src/bitbucket-service.ts +93 -24
- package/src/pr-comment-mapper.ts +13 -1
- package/tsconfig.tsbuildinfo +1 -1
package/src/bitbucket-service.ts
CHANGED
|
@@ -23,6 +23,84 @@ function resolveToken(token: string | (() => string | undefined), missingTokenMe
|
|
|
23
23
|
};
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
+
type DiffLineType = 'ADDED' | 'REMOVED' | 'CONTEXT';
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Build a Bitbucket DC inline comment anchor.
|
|
30
|
+
*
|
|
31
|
+
* For a multiline range, Bitbucket DC (>= 9.3.0) requires `multilineMarker` + `multilineSpan`.
|
|
32
|
+
* The legacy `multilineStartLine`/`multilineStartLineType`/`multilineAnchor`/`multilineDestinationRange`
|
|
33
|
+
* fields are silently ignored by the server, which then stores a single-line anchor — so a multiline
|
|
34
|
+
* ````suggestion` only replaces its first line on Apply. Field names verified against BB DC 9.3.2.
|
|
35
|
+
*/
|
|
36
|
+
function buildCommentAnchor(params: {
|
|
37
|
+
filePath: string;
|
|
38
|
+
line?: number;
|
|
39
|
+
lineType?: DiffLineType;
|
|
40
|
+
startLine?: number;
|
|
41
|
+
startLineType?: DiffLineType;
|
|
42
|
+
}): Record<string, any> {
|
|
43
|
+
const { filePath, line, lineType } = params;
|
|
44
|
+
const anchor: Record<string, any> = { path: filePath, diffType: 'EFFECTIVE' };
|
|
45
|
+
if (line === undefined || !lineType) {
|
|
46
|
+
return anchor;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (params.startLine === undefined) {
|
|
50
|
+
anchor.line = line;
|
|
51
|
+
anchor.lineType = lineType;
|
|
52
|
+
anchor.fileType = lineType === 'REMOVED' ? 'FROM' : 'TO';
|
|
53
|
+
return anchor;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Normalize so the marker sits on the lower line and `line` on the upper line.
|
|
57
|
+
let startLine = params.startLine;
|
|
58
|
+
let startLineType: DiffLineType = params.startLineType ?? lineType;
|
|
59
|
+
let endLine = line;
|
|
60
|
+
let endLineType: DiffLineType = lineType;
|
|
61
|
+
if (startLine > endLine) {
|
|
62
|
+
[startLine, endLine] = [endLine, startLine];
|
|
63
|
+
[startLineType, endLineType] = [endLineType, startLineType];
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const fileType = endLineType === 'REMOVED' ? 'FROM' : 'TO';
|
|
67
|
+
anchor.line = endLine;
|
|
68
|
+
anchor.lineType = endLineType;
|
|
69
|
+
anchor.fileType = fileType;
|
|
70
|
+
anchor.multilineMarker = { startLine, startLineType };
|
|
71
|
+
anchor.multilineSpan =
|
|
72
|
+
fileType === 'FROM'
|
|
73
|
+
? { srcSpanStart: startLine, srcSpanEnd: endLine }
|
|
74
|
+
: { dstSpanStart: startLine, dstSpanEnd: endLine };
|
|
75
|
+
return anchor;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const SUGGESTION_BLOCK_RE = /```suggestion\b[^\n]*\n([\s\S]*?)```/;
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Warn when a multi-line ````suggestion` block is anchored to a single line: Bitbucket only replaces
|
|
82
|
+
* the anchored line on Apply, leaving the rest (duplicated code). Returns undefined when there is no
|
|
83
|
+
* concern. Heuristic — a 1-line anchor with a multi-line suggestion is the common failure shape.
|
|
84
|
+
*/
|
|
85
|
+
function multilineSuggestionWarning(text: string, startLine?: number): string | undefined {
|
|
86
|
+
if (startLine !== undefined) {
|
|
87
|
+
return undefined;
|
|
88
|
+
}
|
|
89
|
+
const match = SUGGESTION_BLOCK_RE.exec(text);
|
|
90
|
+
if (!match) {
|
|
91
|
+
return undefined;
|
|
92
|
+
}
|
|
93
|
+
const suggestionLineCount = match[1].replace(/\n$/, '').split('\n').length;
|
|
94
|
+
if (suggestionLineCount <= 1) {
|
|
95
|
+
return undefined;
|
|
96
|
+
}
|
|
97
|
+
return (
|
|
98
|
+
'The comment contains a multi-line ```suggestion block but is anchored to a single line. ' +
|
|
99
|
+
'On Apply, Bitbucket replaces only that one line and leaves the rest. To replace multiple lines, ' +
|
|
100
|
+
'set startLine (first replaced line) and line (last replaced line) to span the whole range.'
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
|
|
26
104
|
export class BitbucketService {
|
|
27
105
|
private readonly getPageSize: () => number;
|
|
28
106
|
|
|
@@ -349,27 +427,11 @@ export class BitbucketService {
|
|
|
349
427
|
|
|
350
428
|
// Add anchor for file/line comments
|
|
351
429
|
if (filePath) {
|
|
352
|
-
comment.anchor = {
|
|
353
|
-
path: filePath,
|
|
354
|
-
diffType: 'EFFECTIVE'
|
|
355
|
-
};
|
|
356
|
-
|
|
357
|
-
// Add line-specific anchor properties
|
|
358
|
-
if (line !== undefined && lineType) {
|
|
359
|
-
comment.anchor.line = line;
|
|
360
|
-
comment.anchor.lineType = lineType;
|
|
361
|
-
comment.anchor.fileType = 'TO'; // Default to destination file
|
|
362
|
-
|
|
363
|
-
if (startLine !== undefined) {
|
|
364
|
-
const resolvedStartLineType = startLineType ?? lineType;
|
|
365
|
-
comment.anchor.multilineAnchor = true;
|
|
366
|
-
comment.anchor.multilineStartLine = startLine;
|
|
367
|
-
comment.anchor.multilineStartLineType = resolvedStartLineType;
|
|
368
|
-
comment.anchor.multilineDestinationRange = { minimum: startLine, maximum: line };
|
|
369
|
-
}
|
|
370
|
-
}
|
|
430
|
+
comment.anchor = buildCommentAnchor({ filePath, line, lineType, startLine, startLineType });
|
|
371
431
|
}
|
|
372
432
|
|
|
433
|
+
const suggestionWarning = filePath ? multilineSuggestionWarning(text, startLine) : undefined;
|
|
434
|
+
|
|
373
435
|
const result = await handleApiOperation(
|
|
374
436
|
() => PullRequestsService.createComment2(
|
|
375
437
|
projectKey,
|
|
@@ -383,10 +445,17 @@ export class BitbucketService {
|
|
|
383
445
|
if (result.success && result.data && output !== 'full') {
|
|
384
446
|
return {
|
|
385
447
|
...result,
|
|
386
|
-
data:
|
|
448
|
+
data: {
|
|
449
|
+
...shapePullRequestCommentAck(result.data),
|
|
450
|
+
...(suggestionWarning ? { warning: suggestionWarning } : {}),
|
|
451
|
+
},
|
|
387
452
|
};
|
|
388
453
|
}
|
|
389
454
|
|
|
455
|
+
if (result.success && suggestionWarning) {
|
|
456
|
+
return { ...result, warning: suggestionWarning };
|
|
457
|
+
}
|
|
458
|
+
|
|
390
459
|
return result;
|
|
391
460
|
}
|
|
392
461
|
|
|
@@ -911,10 +980,10 @@ export const bitbucketToolSchemas = {
|
|
|
911
980
|
text: z.string().describe("The comment text"),
|
|
912
981
|
parentId: z.number().optional().describe("Parent comment ID for replies"),
|
|
913
982
|
filePath: z.string().optional().describe("File path for file-specific comments"),
|
|
914
|
-
startLine: z.number().optional().describe("
|
|
915
|
-
startLineType: z.enum(['ADDED', 'REMOVED', 'CONTEXT']).optional().describe("Line type for the start line of a multiline
|
|
916
|
-
line: z.number().optional().describe("
|
|
917
|
-
lineType: z.enum(['ADDED', 'REMOVED', 'CONTEXT']).optional().describe("Line type for the end line
|
|
983
|
+
startLine: z.number().optional().describe("First line of a multiline range. Provide together with 'line' (the last line) to span multiple lines. REQUIRED for a multi-line ```suggestion: the anchored range (startLine..line) is exactly what 'Apply suggestion' replaces — omit it and only the single 'line' is replaced, leaving the rest. Requires Bitbucket DC >= 9.3.0 for multiline suggestions."),
|
|
984
|
+
startLineType: z.enum(['ADDED', 'REMOVED', 'CONTEXT']).optional().describe("Line type for the start line of a multiline range. Defaults to the same value as lineType if omitted."),
|
|
985
|
+
line: z.number().optional().describe("Single-line comments: the line. Multiline: the LAST line of the range (use startLine for the first). For a ```suggestion this is the last replaced line."),
|
|
986
|
+
lineType: z.enum(['ADDED', 'REMOVED', 'CONTEXT']).optional().describe("Line type for 'line' (the end line, or the only line for single-line comments). Use 'ADDED'/'CONTEXT' for the new/target file, 'REMOVED' for the original/source file."),
|
|
918
987
|
pending: z.boolean().optional().describe("If true, creates a pending (draft) comment not visible to others until the review is submitted via bitbucket_submitPullRequestReview. Only works when filePath is provided — top-level PR comments (no filePath) are always posted live."),
|
|
919
988
|
severity: z.enum(['NORMAL', 'BLOCKER']).optional().describe("Comment severity. Use 'BLOCKER' to post the comment as a task that must be resolved before the PR can be merged. Defaults to 'NORMAL' (regular comment)."),
|
|
920
989
|
output: z.enum(['ack', 'full']).optional().describe("Return a compact acknowledgement or the full API response. Defaults to ack.")
|
package/src/pr-comment-mapper.ts
CHANGED
|
@@ -24,6 +24,10 @@ interface CommentAnchor {
|
|
|
24
24
|
path: string;
|
|
25
25
|
diffType: string;
|
|
26
26
|
orphaned: boolean;
|
|
27
|
+
multilineMarker?: {
|
|
28
|
+
startLine: number;
|
|
29
|
+
startLineType: string;
|
|
30
|
+
};
|
|
27
31
|
}
|
|
28
32
|
|
|
29
33
|
interface CommentProperties {
|
|
@@ -131,6 +135,8 @@ interface SimplifiedAnchor {
|
|
|
131
135
|
line: number;
|
|
132
136
|
path: string;
|
|
133
137
|
fileType?: string;
|
|
138
|
+
startLine?: number;
|
|
139
|
+
startLineType?: string;
|
|
134
140
|
}
|
|
135
141
|
|
|
136
142
|
interface SimplifiedComment {
|
|
@@ -240,7 +246,13 @@ function simplifyAnchor(anchor: CommentAnchor): SimplifiedAnchor {
|
|
|
240
246
|
return {
|
|
241
247
|
line: anchor.line,
|
|
242
248
|
path: anchor.path,
|
|
243
|
-
fileType: anchor.fileType
|
|
249
|
+
fileType: anchor.fileType,
|
|
250
|
+
...(anchor.multilineMarker
|
|
251
|
+
? {
|
|
252
|
+
startLine: anchor.multilineMarker.startLine,
|
|
253
|
+
startLineType: anchor.multilineMarker.startLineType
|
|
254
|
+
}
|
|
255
|
+
: {})
|
|
244
256
|
};
|
|
245
257
|
}
|
|
246
258
|
|